tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 98bcabadc0b781dbb6be2c0171c1951c3b1d34e0
parent 9c37b4ea73a7f757aad3cd27b48c0517d5ad7f4b
Author: Jon Coppeard <jcoppeard@mozilla.com>
Date:   Sat,  1 Nov 2025 11:58:11 +0000

Bug 1994871 - Part 4: Add tests for gray symbols r=sfink

Differential Revision: https://phabricator.services.mozilla.com/D269956

Diffstat:
Mjs/src/builtin/TestingFunctions.cpp | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajs/src/jit-test/tests/gc/bug-1994871-2.js | 16++++++++++++++++
Ajs/src/jit-test/tests/gc/bug-1994871.js | 40++++++++++++++++++++++++++++++++++++++++
Mjs/src/shell/js.cpp | 60+++++++++++++++++++++++++++++++++---------------------------
Mjs/src/shell/jsshell.h | 12+++++++-----
5 files changed, 172 insertions(+), 32 deletions(-)

diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp @@ -148,6 +148,7 @@ #include "wasm/WasmValue.h" #include "debugger/DebugAPI-inl.h" +#include "gc/AtomMarking-inl.h" #include "jit/JitHints-inl.h" #include "vm/Compartment-inl.h" #include "vm/EnvironmentObject-inl.h" @@ -3206,6 +3207,73 @@ static bool IsAtomMarked(JSContext* cx, unsigned argc, Value* vp) { return true; } +static bool GetAtomMarkIndex(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject callee(cx, &args.callee()); + + if (args.length() != 1 || !args[0].isGCThing() || + !args[0].toGCThing()->zone()->isAtomsZone()) { + ReportUsageErrorASCII(cx, callee, + "Expected an atom as the single argument"); + return false; + } + + gc::TenuredCell* atom = &args[0].toGCThing()->asTenured(); + if ((atom->is<JSString>() && + atom->as<JSString>()->isPermanentAndMayBeShared()) || + (atom->is<JS::Symbol>() && + atom->as<JS::Symbol>()->isPermanentAndMayBeShared())) { + ReportUsageErrorASCII( + cx, callee, "Atom marking bitmap is not used for permanent atoms"); + return false; + } + + if (atom->is<JSString>() && atom->as<JSString>()->asAtom().isPinned()) { + ReportUsageErrorASCII(cx, callee, + "Atom marking bitmap is not used for pinned atoms"); + return false; + } + + size_t index = gc::AtomMarkingRuntime::getAtomBit(atom); + MOZ_RELEASE_ASSERT(index <= INT32_MAX); + args.rval().setInt32(index); + return true; +} + +static bool GetAtomMarkColor(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject callee(cx, &args.callee()); + + if (args.length() != 2) { + ReportUsageErrorASCII(cx, callee, "Expected two arguments"); + return false; + } + + if (!args[0].isObject()) { + ReportUsageErrorASCII(cx, callee, + "Expected an object as the first argument"); + return false; + } + Zone* zone = UncheckedUnwrap(&args[0].toObject())->zone(); + + if (!args[1].isInt32() || args[1].toInt32() < 0) { + ReportUsageErrorASCII(cx, callee, + "Expected a positive integer as the second argument"); + return false; + } + size_t index = args[1].toInt32(); + + gc::GCRuntime* gc = &cx->runtime()->gc; + gc::CellColor color = gc->atomMarking.getAtomMarkColorForIndex(zone, index); + RootedString name(cx, JS_NewStringCopyZ(cx, gc::CellColorName(color))); + if (!name) { + return false; + } + + args.rval().setString(name); + return true; +} + static WeakMapObject* MaybeWeakMapObject(const Value& value) { if (!value.isObject()) { return nullptr; @@ -10387,6 +10455,14 @@ gc::ZealModeHelpText), "isAtomMarked(obj, atom)", " Return whether |atom| is marked relative to the zone containing |obj|."), + JS_FN_HELP("getAtomMarkIndex", GetAtomMarkIndex, 1, 0, +"getAtomMarkIndex(atom)", +" Return the atom marking bitmap's index for |atom|."), + + JS_FN_HELP("getAtomMarkColor", GetAtomMarkColor, 2, 0, +"getAtomMarkColor(obj, index)", +" Return the atom marking bitmap's mark color for |index| relative to the zone containing |obj|."), + JS_FN_HELP("setMallocMaxDirtyPageModifier", SetMallocMaxDirtyPageModifier, 1, 0, "setMallocMaxDirtyPageModifier(value)", " Change the maximum size of jemalloc's page cache. The value should be between\n" diff --git a/js/src/jit-test/tests/gc/bug-1994871-2.js b/js/src/jit-test/tests/gc/bug-1994871-2.js @@ -0,0 +1,16 @@ +// Check a gray symbol keeps a weak map entry alive. + +let wm = new WeakMap(); +s = Symbol(); +wm.set(s, {}); +gc(); +assertEq(nondeterministicGetWeakMapSize(wm), 1); + +grayRoot().push(s); +s = undefined; +gc(); +assertEq(nondeterministicGetWeakMapSize(wm), 1); + +grayRoot().length = 0; +gc(); +assertEq(nondeterministicGetWeakMapSize(wm), 0); diff --git a/js/src/jit-test/tests/gc/bug-1994871.js b/js/src/jit-test/tests/gc/bug-1994871.js @@ -0,0 +1,40 @@ +// Check that atom marking bitmap state and mark color is as expected and +// AtomMarkingRuntime::refineZoneBitmapsForCollectedZones works with gray +// symbols. + +gczeal(0); + +// We'll track the state of a symbol in two zones, |this| and |other|. +let s = Symbol(); +addMarkObservers([s]); +let other = newGlobal({newCompartment: true}); + +// Iniitally we expect the symbol to be marked black in |this| and not marked +// in |other|. +gc(); +let index = getAtomMarkIndex(s); +assertEq(getAtomMarkColor(this, index), 'black'); +assertEq(getAtomMarkColor(other, index), 'white'); +assertEq(getMarks()[0], 'black'); + +// Referencing the symbol in |other| marks it in that zone. +other.s = s; +assertEq(getAtomMarkColor(other, index), 'black'); + +// Replace existing references with a gray reference from |this| and remove +// the reference from |other|. We now expect the mark color to be gray in +// *both* zones. +grayRoot().push(s); +other.s = undefined; +s = undefined; +gc(); +assertEq(getAtomMarkColor(this, index), 'gray'); +assertEq(getAtomMarkColor(other, index), 'gray'); +assertEq(getMarks()[0], 'gray'); + +// Removing the final reference results in the mark color going to white. +grayRoot().length = 0; +gc(); +assertEq(getAtomMarkColor(this, index), 'white'); +assertEq(getAtomMarkColor(other, index), 'white'); +assertEq(getMarks()[0], 'dead'); diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp @@ -211,6 +211,7 @@ #include "wasm/WasmFeatures.h" #include "wasm/WasmJS.h" +#include "gc/WeakMap-inl.h" #include "vm/Compartment-inl.h" #include "vm/ErrorObject-inl.h" #include "vm/Interpreter-inl.h" @@ -8749,7 +8750,7 @@ static MarkBitObservers* EnsureMarkBitObservers(JSContext* cx) { ShellContext* sc = GetShellContext(cx); if (!sc->markObservers) { auto* observers = - cx->new_<MarkBitObservers>(cx->runtime(), NonshrinkingGCObjectVector()); + cx->new_<MarkBitObservers>(cx->runtime(), NonShrinkingValueVector()); if (!observers) { return nullptr; } @@ -8797,19 +8798,17 @@ static bool AddMarkObservers(JSContext* cx, unsigned argc, Value* vp) { } RootedValue value(cx); - RootedObject object(cx); for (uint32_t i = 0; i < length; i++) { if (!JS_GetElement(cx, observersArg, i, &value)) { return false; } - if (!value.isObject()) { - JS_ReportErrorASCII(cx, "argument must be an Array of objects"); + if (!CanBeHeldWeakly(value)) { + JS_ReportErrorASCII(cx, "Can only observe objects and symbols"); return false; } - object = &value.toObject(); - if (gc::IsInsideNursery(object)) { + if (gc::IsInsideNursery(value.toGCThing())) { // WeakCaches are not swept during a minor GC. To prevent // nursery-allocated contents from having the mark bits be deceptively // black until the second GC, they would need to be marked weakly (cf @@ -8818,7 +8817,7 @@ static bool AddMarkObservers(JSContext* cx, unsigned argc, Value* vp) { cx->runtime()->gc.evictNursery(); } - if (!markObservers->get().append(object)) { + if (!markObservers->get().append(value)) { ReportOutOfMemory(cx); return false; } @@ -8828,6 +8827,30 @@ static bool AddMarkObservers(JSContext* cx, unsigned argc, Value* vp) { return true; } +static const char* ObserveMarkColor(const Value& value) { + if (value.isUndefined()) { + return "dead"; + } + + gc::Cell* cell = value.toGCThing(); + Zone* zone = cell->zone(); + if (zone->isGCPreparing()) { + // The mark bits are not valid during unmarking. + return "unmarked"; + } + + gc::TenuredCell* tc = &cell->asTenured(); + if (tc->isMarkedGray()) { + return "gray"; + } + + if (tc->isMarkedBlack()) { + return "black"; + } + + return "unmarked"; +} + static bool GetMarks(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); @@ -8844,27 +8867,10 @@ static bool GetMarks(JSContext* cx, unsigned argc, Value* vp) { } for (uint32_t i = 0; i < length; i++) { - const char* color; - JSObject* obj = observers->get()[i]; - if (!obj) { - color = "dead"; - } else if (obj->zone()->isGCPreparing()) { - color = "unmarked"; - } else { - gc::TenuredCell* cell = &obj->asTenured(); - if (cell->isMarkedGray()) { - color = "gray"; - } else if (cell->isMarkedBlack()) { - color = "black"; - } else { - color = "unmarked"; - } - } + Value value = observers->get()[i]; + const char* color = ObserveMarkColor(value); JSString* s = JS_NewStringCopyZ(cx, color); - if (!s) { - return false; - } - if (!NewbornArrayPush(cx, ret, StringValue(s))) { + if (!s || !NewbornArrayPush(cx, ret, StringValue(s))) { return false; } } diff --git a/js/src/shell/jsshell.h b/js/src/shell/jsshell.h @@ -162,18 +162,20 @@ extern UniqueChars processWideModuleLoadPath; bool CreateAlias(JSContext* cx, const char* dstName, JS::HandleObject namespaceObj, const char* srcName); -class NonshrinkingGCObjectVector - : public GCVector<HeapPtr<JSObject*>, 0, SystemAllocPolicy> { +class NonShrinkingValueVector + : public GCVector<HeapPtr<Value>, 0, SystemAllocPolicy> { + using Base = GCVector<HeapPtr<Value>, 0, SystemAllocPolicy>; + public: bool traceWeak(JSTracer* trc) { - for (HeapPtr<JSObject*>& obj : *this) { - TraceWeakEdge(trc, &obj, "NonshrinkingGCObjectVector element"); + for (HeapPtr<Value>& value : *this) { + TraceWeakEdge(trc, &value, "NonShrinkingValueVector element"); } return true; } }; -using MarkBitObservers = JS::WeakCache<NonshrinkingGCObjectVector>; +using MarkBitObservers = JS::WeakCache<NonShrinkingValueVector>; #ifdef SINGLESTEP_PROFILING using StackChars = Vector<char16_t, 0, SystemAllocPolicy>;