/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/Maybe.h" #include "mozilla/UniquePtr.h" #include "gc/AllocKind.h" #include "gc/Cell.h" #include "gc/GCInternals.h" #include "gc/GCRuntime.h" #include "js/ArrayBuffer.h" // JS::NewArrayBuffer #include "js/experimental/TypedData.h" #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty #include "js/RootingAPI.h" #include "jsapi-tests/tests.h" #include "vm/Runtime.h" #include "vm/TypedArrayObject.h" #include "vm/JSContext-inl.h" using namespace js; static js::gc::CellColor GetColor(JSObject* obj) { return obj->color(); } static js::gc::CellColor GetColor(const JS::ArrayBufferOrView& view) { return view.asObjectUnbarriered()->color(); } static MOZ_MAYBE_UNUSED bool IsInsideNursery(gc::Cell* cell) { return !cell->isTenured(); } static MOZ_MAYBE_UNUSED bool IsInsideNursery( const JS::ArrayBufferOrView& view) { return IsInsideNursery(view.asObjectUnbarriered()); } // A heap-allocated structure containing one of our barriered pointer wrappers // to test. template struct TestStruct { W wrapper; void trace(JSTracer* trc) { TraceNullableEdge(trc, &wrapper, "TestStruct::wrapper"); } TestStruct() {} explicit TestStruct(T init) : wrapper(init) {} }; template static T CreateNurseryGCThing(JSContext* cx) = delete; template <> JSObject* CreateNurseryGCThing(JSContext* cx) { JS::RootedObject obj(cx, JS_NewPlainObject(cx)); if (!obj) { return nullptr; } JS_DefineProperty(cx, obj, "x", 42, 0); MOZ_ASSERT(IsInsideNursery(obj)); return obj; } template <> JSFunction* CreateNurseryGCThing(JSContext* cx) { /* * We don't actually use the function as a function, so here we cheat and * cast a JSObject. */ return static_cast(CreateNurseryGCThing(cx)); } template <> JS::Uint8Array CreateNurseryGCThing(JSContext* cx) { JS::Rooted arr(cx, JS::Uint8Array::create(cx, 100)); JS::RootedObject obj(cx, arr.asObject()); JS_DefineProperty(cx, obj, "x", 42, 0); MOZ_ASSERT(IsInsideNursery(obj)); return arr; } template static T CreateTenuredGCThing(JSContext* cx) = delete; template <> JSObject* CreateTenuredGCThing(JSContext* cx) { // Use ArrayBuffers because they have finalizers, which allows using them in // TenuredHeap<> without awkward conversations about nursery allocatability. // Note that at some point ArrayBuffers might become nursery allocated at // which point this test will have to change. JSObject* obj = JS::NewArrayBuffer(cx, 20); MOZ_ASSERT(!IsInsideNursery(obj)); MOZ_ASSERT(obj->getClass()->hasFinalize() && !(obj->getClass()->flags & JSCLASS_SKIP_NURSERY_FINALIZE)); return obj; } template <> JS::ArrayBuffer CreateTenuredGCThing(JSContext* cx) { return JS::ArrayBuffer::fromObject(CreateTenuredGCThing(cx)); } template <> JS::Uint8Array CreateTenuredGCThing(JSContext* cx) { // Use internal APIs that lets us specify the InitialHeap so we can ensure // that this is tenured. JSObject* obj = js::NewUint8ArrayWithLength(cx, 100, gc::Heap::Tenured); MOZ_ASSERT(!IsInsideNursery(obj)); return JS::Uint8Array::fromObject(obj); } template void* CreateHiddenTenuredGCThing(JSContext* cx) { return CreateTenuredGCThing(cx); } template <> void* CreateHiddenTenuredGCThing(JSContext* cx) { return CreateTenuredGCThing(cx).asObjectUnbarriered(); } template <> void* CreateHiddenTenuredGCThing(JSContext* cx) { return CreateTenuredGCThing(cx).asObjectUnbarriered(); } static uintptr_t UnbarrieredCastToInt(gc::Cell* cell) { return reinterpret_cast(cell); } static uintptr_t UnbarrieredCastToInt(const JS::ArrayBufferOrView& view) { return UnbarrieredCastToInt(view.asObjectUnbarriered()); } template T RecoverHiddenGCThing(void* ptr) { return reinterpret_cast(ptr); } template <> JS::ArrayBuffer RecoverHiddenGCThing(void* ptr) { return JS::ArrayBuffer::fromObject(RecoverHiddenGCThing(ptr)); } template <> JS::Uint8Array RecoverHiddenGCThing(void* ptr) { return JS::Uint8Array::fromObject(RecoverHiddenGCThing(ptr)); } static void MakeGray(JSObject* obj) { gc::TenuredCell* cell = &obj->asTenured(); cell->unmark(); cell->markIfUnmarked(gc::MarkColor::Gray); MOZ_ASSERT(obj->isMarkedGray()); } static void MakeGray(const JS::ArrayBufferOrView& view) { MakeGray(view.asObjectUnbarriered()); } // Test post-barrier implementation on wrapper types. The following wrapper // types have post barriers: // - JS::Heap // - GCPtr // - HeapPtr // - WeakHeapPtr BEGIN_TEST(testGCHeapPostBarriers) { AutoLeaveZeal nozeal(cx); /* Sanity check - objects start in the nursery and then become tenured. */ JS_GC(cx); JS::RootedObject obj(cx, CreateNurseryGCThing(cx)); CHECK(IsInsideNursery(obj.get())); JS_GC(cx); CHECK(!IsInsideNursery(obj.get())); JS::RootedObject tenuredObject(cx, obj); /* JSObject and JSFunction objects are nursery allocated. */ CHECK(TestHeapPostBarriersForType()); CHECK(TestHeapPostBarriersForType()); CHECK(TestHeapPostBarriersForType()); // Bug 1599378: Add string tests. return true; } bool CanAccessObject(JSObject* obj) { JS::RootedObject rootedObj(cx, obj); JS::RootedValue value(cx); CHECK(JS_GetProperty(cx, rootedObj, "x", &value)); CHECK(value.isInt32()); CHECK(value.toInt32() == 42); return true; } bool CanAccessObject(const JS::ArrayBufferOrView& view) { return CanAccessObject(view.asObject()); } template bool TestHeapPostBarriersForType() { CHECK((TestHeapPostBarriersForWrapper())); CHECK((TestHeapPostBarriersForMovableWrapper())); CHECK((TestHeapPostBarriersForMovableWrapper())); CHECK((TestHeapPostBarriersForMovableWrapper())); return true; } template