From a637146e6b229a47e57859642e4c0ffb5f92d860 Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Thu, 30 Jul 2020 16:49:56 +0000 Subject: [PATCH] Bug 1655203 - Add tests for AutoStableStringChars and deduplication r=jonco Differential Revision: https://phabricator.services.mozilla.com/D84872 --- js/src/jit-test/tests/gc/dedupe-02.js | 39 ++++++++++ js/src/jit-test/tests/gc/dedupe.js | 44 +++++++++++ js/src/jsapi-tests/moz.build | 1 + js/src/jsapi-tests/testDeduplication.cpp | 97 ++++++++++++++++++++++++ 4 files changed, 181 insertions(+) create mode 100644 js/src/jit-test/tests/gc/dedupe-02.js create mode 100644 js/src/jit-test/tests/gc/dedupe.js create mode 100644 js/src/jsapi-tests/testDeduplication.cpp diff --git a/js/src/jit-test/tests/gc/dedupe-02.js b/js/src/jit-test/tests/gc/dedupe-02.js new file mode 100644 index 000000000000..72b2f97f9cfd --- /dev/null +++ b/js/src/jit-test/tests/gc/dedupe-02.js @@ -0,0 +1,39 @@ +// AutoStableStringChars needs to prevent the owner of its chars from being +// deduplicated, even if they are held by a different string. + +gczeal(0); + +function makeExtensibleStrFrom(str) { + var left = str.substr(0, str.length/2); + var right = str.substr(str.length/2, str.length); + var ropeStr = left + right; + return ensureLinearString(ropeStr); +} + +// Make a string to deduplicate to. +var original = makeExtensibleStrFrom('{ "phbbbbbbbbbbbbbbttt!!!!??": [1] }\n\n'); + +// Construct D2 -> D1 -> base +var D2 = makeExtensibleStrFrom('{ "phbbbbbbbbbbbbbbttt!!!!??": [1] }'); +var D1 = newRope(D2, '\n', {nursery: true}); +ensureLinearString(D1); +var base = newRope(D1, '\n', {nursery: true}); +ensureLinearString(base); + +// Make an AutoStableStringChars(D2) and do a minor GC within it. (This will do +// a major GC, but it'll start out with a minor GC.) `base` would get +// deduplicated to `original`, if it weren't for AutoStableStringChars marking +// all of D2, D1, and base non-deduplicatable. + +// The first time JSON.parse runs, it will create several (14 in my test) GC +// things before getting to the point where it does an allocation while holding +// the chars pointer. Get them out of the way now. +JSON.parse(D2); + +// Cause a minor GC to happen during JSON.parse after AutoStableStringChars +// gives up its pointer. +schedulegc(1); +JSON.parse(D2); + +// Access `D2` to verify that it is not using the deduplicated chars. +print(D2); diff --git a/js/src/jit-test/tests/gc/dedupe.js b/js/src/jit-test/tests/gc/dedupe.js new file mode 100644 index 000000000000..2e2c578abf55 --- /dev/null +++ b/js/src/jit-test/tests/gc/dedupe.js @@ -0,0 +1,44 @@ +function str(c) { + let s = c; + for (let i = 0; i < 30; i++) + s += c; + ensureLinearString(s); + return s; +} + +function f() { + // Create some slots to write into. + const o = {owner: 'short1', s: 'short2'}; + + // Make a tenured rope. + const r1 = str("a") + str("b"); + gc(); + + // Write the first instance of our duplicate string into one of the slots + // (`owner`). This will be scanned first, and entered into deDupSet when + // tenured. + o.owner = ensureLinearString(str("a") + str("b") + str("c")); + + // Make another rope with identical contents, with a tenured subtree. + const r2 = r1 + str("c"); + + // Linearize the new rope, creating a new extensible string and a bunch of + // dependent strings replacing the rest of the rope nodes. + ensureLinearString(r2); + + // Write the new rope into a slot, so that it will be scanned next during the + // minor GC during traceSlots(). + o.s = r2; + + // Do a nursery collection. o.owner will be tenured and inserted into + // deDupSet. Then o.s aka r2 will be tenured. If things work correctly, r2 + // will be marked non-deduplicatable because it is the base of a tenured + // string r1. If not, it will be deduplicated to o.owner. + minorgc(); + + // Extract out that r1 child node. If its base was deduplicated, this will + // assert because its chars have been freed. + const s1 = r1.substr(0, 31); +} + +f(); diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build index a1fad10ced74..2760a13950c1 100644 --- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -28,6 +28,7 @@ UNIFIED_SOURCES += [ 'testCompileUtf8.cpp', 'testDateToLocaleString.cpp', 'testDebugger.cpp', + 'testDeduplication.cpp', 'testDeepFreeze.cpp', 'testDefineGetterSetterNonEnumerable.cpp', 'testDefineProperty.cpp', diff --git a/js/src/jsapi-tests/testDeduplication.cpp b/js/src/jsapi-tests/testDeduplication.cpp new file mode 100644 index 000000000000..b81e0993b5e1 --- /dev/null +++ b/js/src/jsapi-tests/testDeduplication.cpp @@ -0,0 +1,97 @@ +/* -*- 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 + +#include "jsfriendapi.h" + +#include "js/RootingAPI.h" +#include "js/StableStringChars.h" + +#include "jsapi-tests/tests.h" + +#include "vm/JSContext.h" +#include "vm/StringType.h" + +#include "vm/JSContext-inl.h" + +static bool SameChars(JSContext* cx, JSString* str1, JSString* str2, + size_t offset) { + JS::AutoCheckCannotGC nogc(cx); + + const JS::Latin1Char* chars1 = + js::StringToLinearString(cx, str1)->latin1Chars(nogc); + const JS::Latin1Char* chars2 = + js::StringToLinearString(cx, str2)->latin1Chars(nogc); + + return chars1 == chars2 + offset; +} + +BEGIN_TEST(testDeduplication_ASSC) { + // Test with a long enough string to avoid inline chars allocation. + const char text[] = + "Andthebeastshallcomeforthsurroundedbyaroilingcloudofvengeance." + "Thehouseoftheunbelieversshallberazedandtheyshallbescorchedtoth" + "eearth.Theirtagsshallblinkuntiltheendofdays."; + + // Create a string to deduplicate later strings to. + JS::RootedString original(cx, JS_NewStringCopyZ(cx, text)); + CHECK(original); + + // Create a chain of dependent strings, with a base string whose contents + // match `original`'s. + JS::RootedString str(cx, JS_NewStringCopyZ(cx, text)); + CHECK(str); + + JS::RootedString dep(cx, JS_NewDependentString(cx, str, 10, 100)); + CHECK(str); + + JS::RootedString depdep(cx, JS_NewDependentString(cx, dep, 10, 80)); + CHECK(str); + + // Repeat. This one will not be prevented from deduplication. + JS::RootedString str2(cx, JS_NewStringCopyZ(cx, text)); + CHECK(str); + + JS::RootedString dep2(cx, JS_NewDependentString(cx, str2, 10, 100)); + CHECK(str); + + JS::RootedString depdep2(cx, JS_NewDependentString(cx, dep2, 10, 80)); + CHECK(str); + + // Initializing an AutoStableStringChars with `depdep` should prevent the + // owner of its chars (`str`) from deduplication. + JS::AutoStableStringChars stable(cx); + CHECK(stable.init(cx, depdep)); + + const JS::Latin1Char* chars = stable.latin1Chars(); + CHECK(memcmp(chars, text + 20, 80 * sizeof(JS::Latin1Char)) == 0); + + // `depdep` should share chars with `str` but not with `original`. + CHECK(SameChars(cx, depdep, str, 20)); + CHECK(!SameChars(cx, depdep, original, 20)); + + // Same for `depdep2`. + CHECK(SameChars(cx, depdep2, str2, 20)); + CHECK(!SameChars(cx, depdep2, original, 20)); + + // Do a minor GC that will deduplicate `str2` to `original`, and would have + // deduplicated `str` as well if it weren't prevented by the + // AutoStableStringChars. + cx->minorGC(JS::GCReason::API); + + // `depdep` should still share chars with `str` but not with `original`. + CHECK(SameChars(cx, depdep, str, 20)); + CHECK(!SameChars(cx, depdep, original, 20)); + + // `depdep2` should now share chars with both `str` and `original`. + CHECK(SameChars(cx, depdep2, str2, 20)); + CHECK(SameChars(cx, depdep2, original, 20)); + + return true; +} +END_TEST(testDeduplication_ASSC)