tor-browser

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

testGCHooks.cpp (10288B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "mozilla/ScopeExit.h"
      6 #include "mozilla/UniquePtr.h"
      7 
      8 #include "jsapi-tests/tests.h"
      9 
     10 static unsigned gSliceCallbackCount = 0;
     11 static bool gSawAllSliceCallbacks;
     12 static bool gSawAllGCCallbacks;
     13 
     14 static void NonIncrementalGCSliceCallback(JSContext* cx,
     15                                          JS::GCProgress progress,
     16                                          const JS::GCDescription& desc) {
     17  using namespace JS;
     18  static GCProgress expect[] = {GC_CYCLE_BEGIN, GC_SLICE_BEGIN, GC_SLICE_END,
     19                                GC_CYCLE_END};
     20 
     21  MOZ_RELEASE_ASSERT(gSliceCallbackCount < std::size(expect));
     22  MOZ_RELEASE_ASSERT(progress == expect[gSliceCallbackCount++]);
     23  MOZ_RELEASE_ASSERT(desc.isZone_ == false);
     24  MOZ_RELEASE_ASSERT(desc.options_ == JS::GCOptions::Normal);
     25  MOZ_RELEASE_ASSERT(desc.reason_ == JS::GCReason::API);
     26  if (progress == GC_CYCLE_END) {
     27    mozilla::UniquePtr<char16_t> summary(desc.formatSummaryMessage(cx));
     28    mozilla::UniquePtr<char16_t> message(desc.formatSliceMessage(cx));
     29  }
     30 }
     31 
     32 BEGIN_TEST(testGCSliceCallback) {
     33  gSliceCallbackCount = 0;
     34  JS::SetGCSliceCallback(cx, NonIncrementalGCSliceCallback);
     35  JS_GC(cx);
     36  JS::SetGCSliceCallback(cx, nullptr);
     37  CHECK(gSliceCallbackCount == 4);
     38  return true;
     39 }
     40 END_TEST(testGCSliceCallback)
     41 
     42 static void RootsRemovedGCSliceCallback(JSContext* cx, JS::GCProgress progress,
     43                                        const JS::GCDescription& desc) {
     44  using namespace JS;
     45 
     46  static constexpr struct {
     47    GCProgress progress;
     48    GCReason reason;
     49  } expect[] = {
     50      // Explicitly requested a full GC.
     51      {GC_CYCLE_BEGIN, GCReason::DEBUG_GC},
     52      {GC_SLICE_BEGIN, GCReason::DEBUG_GC},
     53      {GC_SLICE_END, GCReason::DEBUG_GC},
     54      {GC_SLICE_BEGIN, GCReason::DEBUG_GC},
     55      {GC_SLICE_END, GCReason::DEBUG_GC},
     56      {GC_CYCLE_END, GCReason::DEBUG_GC},
     57      // Shutdown GC with ROOTS_REMOVED.
     58      {GC_CYCLE_BEGIN, GCReason::ROOTS_REMOVED},
     59      {GC_SLICE_BEGIN, GCReason::ROOTS_REMOVED},
     60      {GC_SLICE_END, GCReason::ROOTS_REMOVED},
     61      {GC_CYCLE_END, GCReason::ROOTS_REMOVED}
     62      // All done.
     63  };
     64 
     65  MOZ_RELEASE_ASSERT(gSliceCallbackCount < std::size(expect));
     66  MOZ_RELEASE_ASSERT(progress == expect[gSliceCallbackCount].progress);
     67  MOZ_RELEASE_ASSERT(desc.isZone_ == false);
     68  MOZ_RELEASE_ASSERT(desc.options_ == JS::GCOptions::Shrink);
     69  MOZ_RELEASE_ASSERT(desc.reason_ == expect[gSliceCallbackCount].reason);
     70  gSliceCallbackCount++;
     71 }
     72 
     73 BEGIN_TEST(testGCRootsRemoved) {
     74  AutoLeaveZeal nozeal(cx);
     75 
     76  AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
     77 
     78  gSliceCallbackCount = 0;
     79  JS::SetGCSliceCallback(cx, RootsRemovedGCSliceCallback);
     80  auto byebye = mozilla::MakeScopeExit(
     81      [=, this] { JS::SetGCSliceCallback(cx, nullptr); });
     82 
     83  JS::RootedObject obj(cx, JS_NewPlainObject(cx));
     84  CHECK(obj);
     85 
     86  JS::PrepareForFullGC(cx);
     87  JS::SliceBudget budget(JS::WorkBudget(1));
     88  cx->runtime()->gc.startDebugGC(JS::GCOptions::Shrink, budget);
     89  CHECK(JS::IsIncrementalGCInProgress(cx));
     90 
     91  // Trigger another GC after the current one in shrinking / shutdown GCs.
     92  cx->runtime()->gc.notifyRootsRemoved();
     93 
     94  JS::FinishIncrementalGC(cx, JS::GCReason::DEBUG_GC);
     95  CHECK(!JS::IsIncrementalGCInProgress(cx));
     96 
     97  return true;
     98 }
     99 END_TEST(testGCRootsRemoved)
    100 
    101 #define ASSERT_MSG(cond, ...)       \
    102  do {                              \
    103    if (!(cond)) {                  \
    104      fprintf(stderr, __VA_ARGS__); \
    105      MOZ_RELEASE_ASSERT(cond);     \
    106    }                               \
    107  } while (false)
    108 
    109 // Trigger some nested GCs to ensure that they get their own reasons and
    110 // fullGCRequested state.
    111 //
    112 // The callbacks will form the following tree:
    113 //
    114 //   Begin(DEBUG_GC)
    115 //     Begin(API)
    116 //     End(API)
    117 //   End(DEBUG_GC)
    118 //     Begin(MEM_PRESSURE)
    119 //     End(MEM_PRESSURE)
    120 //       Begin(DOM_WINDOW_UTILS)
    121 //       End(DOM_WINDOW_UTILS)
    122 //
    123 // JSGC_BEGIN and JSGC_END callbacks will be observed as a preorder traversal
    124 // of the above tree.
    125 //
    126 // Note that the ordering of the *slice* callbacks don't match up simply to the
    127 // ordering above. If a JSGC_BEGIN triggers another GC, we won't see the outer
    128 // GC's GC_CYCLE_BEGIN until the inner one is done. The slice callbacks are
    129 // reporting the actual order that the GCs are happening in.
    130 //
    131 // JSGC_END, on the other hand, won't be emitted until the GC is complete and
    132 // the GC_CYCLE_BEGIN callback has fired. So its ordering is straightforward.
    133 //
    134 static void GCTreeCallback(JSContext* cx, JSGCStatus status,
    135                           JS::GCReason reason, void* data) {
    136  using namespace JS;
    137 
    138  static constexpr struct {
    139    JSGCStatus expectedStatus;
    140    JS::GCReason expectedReason;
    141    bool fireGC;
    142    JS::GCReason reason;
    143    bool requestFullGC;
    144  } invocations[] = {
    145      {JSGC_BEGIN, GCReason::DEBUG_GC, true, GCReason::API, false},
    146      {JSGC_BEGIN, GCReason::API, false},
    147      {JSGC_END, GCReason::API, false},
    148      {JSGC_END, GCReason::DEBUG_GC, true, GCReason::MEM_PRESSURE, true},
    149      {JSGC_BEGIN, GCReason::MEM_PRESSURE, false},
    150      {JSGC_END, GCReason::MEM_PRESSURE, true, GCReason::DOM_WINDOW_UTILS,
    151       false},
    152      {JSGC_BEGIN, GCReason::DOM_WINDOW_UTILS, false},
    153      {JSGC_END, GCReason::DOM_WINDOW_UTILS, false}};
    154 
    155  static size_t i = 0;
    156  MOZ_RELEASE_ASSERT(i < std::size(invocations));
    157  auto& invocation = invocations[i++];
    158  if (i == std::size(invocations)) {
    159    gSawAllGCCallbacks = true;
    160  }
    161  ASSERT_MSG(status == invocation.expectedStatus,
    162             "GC callback #%zu: got status %d expected %d\n", i, status,
    163             invocation.expectedStatus);
    164  ASSERT_MSG(reason == invocation.expectedReason,
    165             "GC callback #%zu: got reason %s expected %s\n", i,
    166             ExplainGCReason(reason),
    167             ExplainGCReason(invocation.expectedReason));
    168  if (invocation.fireGC) {
    169    if (invocation.requestFullGC) {
    170      JS::PrepareForFullGC(cx);
    171    }
    172    JS::SliceBudget budget = JS::SliceBudget(JS::WorkBudget(1));
    173    cx->runtime()->gc.startGC(GCOptions::Normal, invocation.reason, budget);
    174    MOZ_RELEASE_ASSERT(JS::IsIncrementalGCInProgress(cx));
    175 
    176    JS::FinishIncrementalGC(cx, invocation.reason);
    177    MOZ_RELEASE_ASSERT(!JS::IsIncrementalGCInProgress(cx));
    178  }
    179 }
    180 
    181 static void GCTreeSliceCallback(JSContext* cx, JS::GCProgress progress,
    182                                const JS::GCDescription& desc) {
    183  using namespace JS;
    184 
    185  static constexpr struct {
    186    GCProgress progress;
    187    GCReason reason;
    188    bool isZonal;
    189  } expectations[] = {
    190      // JSGC_BEGIN triggers a new GC before we get any slice callbacks from the
    191      // original outer GC. So the very first reason observed is API, not
    192      // DEBUG_GC.
    193      {GC_CYCLE_BEGIN, GCReason::API, true},
    194      {GC_SLICE_BEGIN, GCReason::API, true},
    195      {GC_SLICE_END, GCReason::API, true},
    196      {GC_SLICE_BEGIN, GCReason::API, true},
    197      {GC_SLICE_END, GCReason::API, true},
    198      {GC_CYCLE_END, GCReason::API, true},
    199      // Now the "outer" GC runs. It requested a full GC.
    200      {GC_CYCLE_BEGIN, GCReason::DEBUG_GC, false},
    201      {GC_SLICE_BEGIN, GCReason::DEBUG_GC, false},
    202      {GC_SLICE_END, GCReason::DEBUG_GC, false},
    203      {GC_SLICE_BEGIN, GCReason::DEBUG_GC, false},
    204      {GC_SLICE_END, GCReason::DEBUG_GC, false},
    205      {GC_CYCLE_END, GCReason::DEBUG_GC, false},
    206      // The outer JSGC_DEBUG GC's end callback triggers a full MEM_PRESSURE
    207      // GC, which runs next. (Its JSGC_BEGIN does not run a GC.)
    208      {GC_CYCLE_BEGIN, GCReason::MEM_PRESSURE, false},
    209      {GC_SLICE_BEGIN, GCReason::MEM_PRESSURE, false},
    210      {GC_SLICE_END, GCReason::MEM_PRESSURE, false},
    211      {GC_SLICE_BEGIN, GCReason::MEM_PRESSURE, false},
    212      {GC_SLICE_END, GCReason::MEM_PRESSURE, false},
    213      {GC_CYCLE_END, GCReason::MEM_PRESSURE, false},
    214      // The MEM_PRESSURE's GC's end callback now triggers a (zonal)
    215      // DOM_WINDOW_UTILS GC.
    216      {GC_CYCLE_BEGIN, GCReason::DOM_WINDOW_UTILS, true},
    217      {GC_SLICE_BEGIN, GCReason::DOM_WINDOW_UTILS, true},
    218      {GC_SLICE_END, GCReason::DOM_WINDOW_UTILS, true},
    219      {GC_SLICE_BEGIN, GCReason::DOM_WINDOW_UTILS, true},
    220      {GC_SLICE_END, GCReason::DOM_WINDOW_UTILS, true},
    221      {GC_CYCLE_END, GCReason::DOM_WINDOW_UTILS, true},
    222      // All done.
    223  };
    224 
    225  MOZ_RELEASE_ASSERT(gSliceCallbackCount < std::size(expectations));
    226  auto& expect = expectations[gSliceCallbackCount];
    227  ASSERT_MSG(progress == expect.progress, "iteration %d: wrong progress\n",
    228             gSliceCallbackCount);
    229  ASSERT_MSG(desc.reason_ == expect.reason,
    230             "iteration %d: expected %s got %s\n", gSliceCallbackCount,
    231             JS::ExplainGCReason(expect.reason),
    232             JS::ExplainGCReason(desc.reason_));
    233  ASSERT_MSG(desc.isZone_ == expect.isZonal, "iteration %d: wrong zonal\n",
    234             gSliceCallbackCount);
    235  MOZ_RELEASE_ASSERT(desc.options_ == JS::GCOptions::Normal);
    236  gSliceCallbackCount++;
    237  if (gSliceCallbackCount == std::size(expectations)) {
    238    gSawAllSliceCallbacks = true;
    239  }
    240 }
    241 
    242 BEGIN_TEST(testGCTree) {
    243  AutoLeaveZeal nozeal(cx);
    244 
    245  AutoGCParameter param1(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
    246 
    247  gSliceCallbackCount = 0;
    248  gSawAllSliceCallbacks = false;
    249  gSawAllGCCallbacks = false;
    250  JS::SetGCSliceCallback(cx, GCTreeSliceCallback);
    251  JS_SetGCCallback(cx, GCTreeCallback, nullptr);
    252 
    253  // Automate the callback clearing. Otherwise if a CHECK fails, it will get
    254  // cluttered with additional failures from the callback unexpectedly firing
    255  // during the final shutdown GC.
    256  auto byebye = mozilla::MakeScopeExit([=, this] {
    257    JS::SetGCSliceCallback(cx, nullptr);
    258    JS_SetGCCallback(cx, nullptr, nullptr);
    259  });
    260 
    261  JS::RootedObject obj(cx, JS_NewPlainObject(cx));
    262  CHECK(obj);
    263 
    264  // Outer GC is a full GC.
    265  JS::PrepareForFullGC(cx);
    266  JS::SliceBudget budget(JS::WorkBudget(1));
    267  cx->runtime()->gc.startDebugGC(JS::GCOptions::Normal, budget);
    268  CHECK(JS::IsIncrementalGCInProgress(cx));
    269 
    270  JS::FinishIncrementalGC(cx, JS::GCReason::DEBUG_GC);
    271  CHECK(!JS::IsIncrementalGCInProgress(cx));
    272  CHECK(gSawAllSliceCallbacks);
    273  CHECK(gSawAllGCCallbacks);
    274 
    275  return true;
    276 }
    277 END_TEST(testGCTree)