tor-browser

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

testSavedStacks.cpp (14586B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/Utf8.h"  // mozilla::Utf8Unit
      8 
      9 #include "builtin/TestingFunctions.h"
     10 #include "js/ColumnNumber.h"  // JS::LimitedColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin
     11 #include "js/CompilationAndEvaluation.h"  // JS::Evaluate
     12 #include "js/Exception.h"
     13 #include "js/SavedFrameAPI.h"
     14 #include "js/SourceText.h"  // JS::Source{Ownership,Text}
     15 #include "js/Stack.h"
     16 #include "jsapi-tests/tests.h"
     17 #include "util/Text.h"
     18 #include "vm/ArrayObject.h"
     19 #include "vm/Realm.h"
     20 #include "vm/SavedStacks.h"
     21 
     22 BEGIN_TEST(testSavedStacks_withNoStack) {
     23  JS::Realm* realm = cx->realm();
     24  realm->setAllocationMetadataBuilder(&js::SavedStacks::metadataBuilder);
     25  JS::RootedObject obj(cx, js::NewDenseEmptyArray(cx));
     26  realm->setAllocationMetadataBuilder(nullptr);
     27  return true;
     28 }
     29 END_TEST(testSavedStacks_withNoStack)
     30 
     31 BEGIN_TEST(testSavedStacks_ApiDefaultValues) {
     32  JS::Rooted<js::SavedFrame*> savedFrame(cx, nullptr);
     33 
     34  JSPrincipals* principals = cx->realm()->principals();
     35 
     36  // Source
     37  JS::RootedString str(cx);
     38  JS::SavedFrameResult result =
     39      JS::GetSavedFrameSource(cx, principals, savedFrame, &str);
     40  CHECK(result == JS::SavedFrameResult::AccessDenied);
     41  CHECK(str.get() == cx->runtime()->emptyString);
     42 
     43  // Line
     44  uint32_t line = 123;
     45  result = JS::GetSavedFrameLine(cx, principals, savedFrame, &line);
     46  CHECK(result == JS::SavedFrameResult::AccessDenied);
     47  CHECK(line == 0);
     48 
     49  // Column
     50  JS::TaggedColumnNumberOneOrigin column(JS::LimitedColumnNumberOneOrigin(123));
     51  result = JS::GetSavedFrameColumn(cx, principals, savedFrame, &column);
     52  CHECK(result == JS::SavedFrameResult::AccessDenied);
     53  CHECK(column == JS::TaggedColumnNumberOneOrigin());
     54 
     55  // Function display name
     56  result =
     57      JS::GetSavedFrameFunctionDisplayName(cx, principals, savedFrame, &str);
     58  CHECK(result == JS::SavedFrameResult::AccessDenied);
     59  CHECK(str.get() == nullptr);
     60 
     61  // Parent
     62  JS::RootedObject parent(cx);
     63  result = JS::GetSavedFrameParent(cx, principals, savedFrame, &parent);
     64  CHECK(result == JS::SavedFrameResult::AccessDenied);
     65  CHECK(parent.get() == nullptr);
     66 
     67  // Stack string
     68  CHECK(JS::BuildStackString(cx, principals, savedFrame, &str));
     69  CHECK(str.get() == cx->runtime()->emptyString);
     70 
     71  return true;
     72 }
     73 END_TEST(testSavedStacks_ApiDefaultValues)
     74 
     75 BEGIN_TEST(testSavedStacks_RangeBasedForLoops) {
     76  CHECK(js::DefineTestingFunctions(cx, global, false, false));
     77 
     78  JS::RootedValue val(cx);
     79  CHECK(
     80      evaluate("(function one() {                      \n"   // 1
     81               "  return (function two() {             \n"   // 2
     82               "    return (function three() {         \n"   // 3
     83               "      return saveStack();              \n"   // 4
     84               "    }());                              \n"   // 5
     85               "  }());                                \n"   // 6
     86               "}());                                  \n",  // 7
     87               "filename.js", 1, &val));
     88 
     89  CHECK(val.isObject());
     90  JS::RootedObject obj(cx, &val.toObject());
     91 
     92  CHECK(obj->is<js::SavedFrame>());
     93  JS::Rooted<js::SavedFrame*> savedFrame(cx, &obj->as<js::SavedFrame>());
     94 
     95  JS::Rooted<js::SavedFrame*> rf(cx, savedFrame);
     96  for (JS::Handle<js::SavedFrame*> frame :
     97       js::SavedFrame::RootedRange(cx, rf)) {
     98    JS_GC(cx);
     99    CHECK(frame == rf);
    100    rf = rf->getParent();
    101  }
    102  CHECK(rf == nullptr);
    103 
    104  // Stack string
    105  static const char SpiderMonkeyStack[] =
    106      "three@filename.js:4:14\n"
    107      "two@filename.js:5:6\n"
    108      "one@filename.js:6:4\n"
    109      "@filename.js:7:2\n";
    110  static const char V8Stack[] =
    111      "    at three (filename.js:4:14)\n"
    112      "    at two (filename.js:5:6)\n"
    113      "    at one (filename.js:6:4)\n"
    114      "    at filename.js:7:2";
    115  struct {
    116    js::StackFormat format;
    117    const char* expected;
    118  } expectations[] = {{js::StackFormat::Default, SpiderMonkeyStack},
    119                      {js::StackFormat::SpiderMonkey, SpiderMonkeyStack},
    120                      {js::StackFormat::V8, V8Stack}};
    121  auto CheckStacks = [&]() {
    122    for (auto& expectation : expectations) {
    123      JS::RootedString str(cx);
    124      JSPrincipals* principals = cx->realm()->principals();
    125      CHECK(JS::BuildStackString(cx, principals, savedFrame, &str, 0,
    126                                 expectation.format));
    127      JSLinearString* lin = str->ensureLinear(cx);
    128      CHECK(lin);
    129      CHECK(js::StringEqualsAscii(lin, expectation.expected));
    130    }
    131    return true;
    132  };
    133 
    134  CHECK(CheckStacks());
    135 
    136  js::SetStackFormat(cx, js::StackFormat::V8);
    137  expectations[0].expected = V8Stack;
    138 
    139  CHECK(CheckStacks());
    140 
    141  return true;
    142 }
    143 END_TEST(testSavedStacks_RangeBasedForLoops)
    144 
    145 BEGIN_TEST(testSavedStacks_ErrorStackSpiderMonkey) {
    146  JS::RootedValue val(cx);
    147  CHECK(
    148      evaluate("(function one() {                      \n"   // 1
    149               "  return (function two() {             \n"   // 2
    150               "    return (function three() {         \n"   // 3
    151               "      return new Error('foo');         \n"   // 4
    152               "    }());                              \n"   // 5
    153               "  }());                                \n"   // 6
    154               "}()).stack                             \n",  // 7
    155               "filename.js", 1, &val));
    156 
    157  CHECK(val.isString());
    158  JS::RootedString stack(cx, val.toString());
    159 
    160  // Stack string
    161  static const char SpiderMonkeyStack[] =
    162      "three@filename.js:4:14\n"
    163      "two@filename.js:5:6\n"
    164      "one@filename.js:6:4\n"
    165      "@filename.js:7:2\n";
    166  JSLinearString* lin = stack->ensureLinear(cx);
    167  CHECK(lin);
    168  CHECK(js::StringEqualsLiteral(lin, SpiderMonkeyStack));
    169 
    170  return true;
    171 }
    172 END_TEST(testSavedStacks_ErrorStackSpiderMonkey)
    173 
    174 BEGIN_TEST(testSavedStacks_ErrorStackV8) {
    175  js::SetStackFormat(cx, js::StackFormat::V8);
    176 
    177  JS::RootedValue val(cx);
    178  CHECK(
    179      evaluate("(function one() {                      \n"   // 1
    180               "  return (function two() {             \n"   // 2
    181               "    return (function three() {         \n"   // 3
    182               "      return new Error('foo');         \n"   // 4
    183               "    }());                              \n"   // 5
    184               "  }());                                \n"   // 6
    185               "}()).stack                             \n",  // 7
    186               "filename.js", 1, &val));
    187 
    188  CHECK(val.isString());
    189  JS::RootedString stack(cx, val.toString());
    190 
    191  // Stack string
    192  static const char V8Stack[] =
    193      "Error: foo\n"
    194      "    at three (filename.js:4:14)\n"
    195      "    at two (filename.js:5:6)\n"
    196      "    at one (filename.js:6:4)\n"
    197      "    at filename.js:7:2";
    198  JSLinearString* lin = stack->ensureLinear(cx);
    199  CHECK(lin);
    200  CHECK(js::StringEqualsLiteral(lin, V8Stack));
    201 
    202  return true;
    203 }
    204 END_TEST(testSavedStacks_ErrorStackV8)
    205 
    206 BEGIN_TEST(testSavedStacks_selfHostedFrames) {
    207  CHECK(js::DefineTestingFunctions(cx, global, false, false));
    208 
    209  JS::RootedValue val(cx);
    210  //             0         1         2         3
    211  //             0123456789012345678901234567890123456789
    212  CHECK(
    213      evaluate("(function one() {                      \n"   // 1
    214               "  try {                                \n"   // 2
    215               "    [1].map(function two() {           \n"   // 3
    216               "      throw saveStack();               \n"   // 4
    217               "    });                                \n"   // 5
    218               "  } catch (stack) {                    \n"   // 6
    219               "    return stack;                      \n"   // 7
    220               "  }                                    \n"   // 8
    221               "}())                                   \n",  // 9
    222               "filename.js", 1, &val));
    223 
    224  CHECK(val.isObject());
    225  JS::RootedObject obj(cx, &val.toObject());
    226 
    227  CHECK(obj->is<js::SavedFrame>());
    228  JS::Rooted<js::SavedFrame*> savedFrame(cx, &obj->as<js::SavedFrame>());
    229 
    230  JS::Rooted<js::SavedFrame*> selfHostedFrame(cx, savedFrame->getParent());
    231  CHECK(selfHostedFrame->isSelfHosted(cx));
    232 
    233  JSPrincipals* principals = cx->realm()->principals();
    234 
    235  // Source
    236  JS::RootedString str(cx);
    237  JS::SavedFrameResult result = JS::GetSavedFrameSource(
    238      cx, principals, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Exclude);
    239  CHECK(result == JS::SavedFrameResult::Ok);
    240  JSLinearString* lin = str->ensureLinear(cx);
    241  CHECK(lin);
    242  CHECK(js::StringEqualsLiteral(lin, "filename.js"));
    243 
    244  // Source, including self-hosted frames
    245  result = JS::GetSavedFrameSource(cx, principals, selfHostedFrame, &str,
    246                                   JS::SavedFrameSelfHosted::Include);
    247  CHECK(result == JS::SavedFrameResult::Ok);
    248  lin = str->ensureLinear(cx);
    249  CHECK(lin);
    250  CHECK(js::StringEqualsLiteral(lin, "self-hosted"));
    251 
    252  // Line
    253  uint32_t line = 123;
    254  result = JS::GetSavedFrameLine(cx, principals, selfHostedFrame, &line,
    255                                 JS::SavedFrameSelfHosted::Exclude);
    256  CHECK(result == JS::SavedFrameResult::Ok);
    257  CHECK_EQUAL(line, 3U);
    258 
    259  // Column
    260  JS::TaggedColumnNumberOneOrigin column(JS::LimitedColumnNumberOneOrigin(123));
    261  result = JS::GetSavedFrameColumn(cx, principals, selfHostedFrame, &column,
    262                                   JS::SavedFrameSelfHosted::Exclude);
    263  CHECK(result == JS::SavedFrameResult::Ok);
    264  CHECK_EQUAL(column.oneOriginValue(), 9U);
    265 
    266  // Function display name
    267  result = JS::GetSavedFrameFunctionDisplayName(
    268      cx, principals, selfHostedFrame, &str, JS::SavedFrameSelfHosted::Exclude);
    269  CHECK(result == JS::SavedFrameResult::Ok);
    270  lin = str->ensureLinear(cx);
    271  CHECK(lin);
    272  CHECK(js::StringEqualsLiteral(lin, "one"));
    273 
    274  // Parent
    275  JS::RootedObject parent(cx);
    276  result = JS::GetSavedFrameParent(cx, principals, savedFrame, &parent,
    277                                   JS::SavedFrameSelfHosted::Exclude);
    278  CHECK(result == JS::SavedFrameResult::Ok);
    279  // JS::GetSavedFrameParent does this super funky and potentially unexpected
    280  // thing where it doesn't return the next subsumed parent but any next
    281  // parent. This so that callers can still get the "asyncParent" property
    282  // which is only on the first frame of the async parent stack and that frame
    283  // might not be subsumed by the caller. It is expected that callers will
    284  // still interact with the frame through the JSAPI accessors, so this should
    285  // be safe and should not leak privileged info to unprivileged
    286  // callers. However, because of that, we don't test that the parent we get
    287  // here is the selfHostedFrame's parent (because, as just explained, it
    288  // isn't) and instead check that asking for the source property gives us the
    289  // expected value.
    290  result = JS::GetSavedFrameSource(cx, principals, parent, &str,
    291                                   JS::SavedFrameSelfHosted::Exclude);
    292  CHECK(result == JS::SavedFrameResult::Ok);
    293  lin = str->ensureLinear(cx);
    294  CHECK(lin);
    295  CHECK(js::StringEqualsLiteral(lin, "filename.js"));
    296 
    297  return true;
    298 }
    299 END_TEST(testSavedStacks_selfHostedFrames)
    300 
    301 BEGIN_TEST(test_GetPendingExceptionStack) {
    302  CHECK(js::DefineTestingFunctions(cx, global, false, false));
    303 
    304  JSPrincipals* principals = cx->realm()->principals();
    305 
    306  static const char sourceText[] =
    307      //          1         2         3
    308      // 123456789012345678901234567890123456789
    309      "(function one() {                      \n"   // 1
    310      "  (function two() {                    \n"   // 2
    311      "    (function three() {                \n"   // 3
    312      "      throw 5;                         \n"   // 4
    313      "    }());                              \n"   // 5
    314      "  }());                                \n"   // 6
    315      "}())                                   \n";  // 7
    316 
    317  JS::CompileOptions opts(cx);
    318  opts.setFileAndLine("filename.js", 1U);
    319 
    320  JS::SourceText<mozilla::Utf8Unit> srcBuf;
    321  CHECK(srcBuf.init(cx, sourceText, js_strlen(sourceText),
    322                    JS::SourceOwnership::Borrowed));
    323 
    324  JS::RootedValue val(cx);
    325  bool ok = JS::Evaluate(cx, opts, srcBuf, &val);
    326 
    327  CHECK(!ok);
    328  CHECK(JS_IsExceptionPending(cx));
    329  CHECK(val.isUndefined());
    330 
    331  JS::ExceptionStack exnStack(cx);
    332  CHECK(JS::GetPendingExceptionStack(cx, &exnStack));
    333  CHECK(exnStack.stack());
    334  CHECK(exnStack.stack()->is<js::SavedFrame>());
    335  JS::Rooted<js::SavedFrame*> savedFrameStack(
    336      cx, &exnStack.stack()->as<js::SavedFrame>());
    337 
    338  CHECK(exnStack.exception().isInt32());
    339  CHECK(exnStack.exception().toInt32() == 5);
    340 
    341  struct {
    342    uint32_t line;
    343    uint32_t column;
    344    const char* source;
    345    const char* functionDisplayName;
    346  } expected[] = {{4, 7, "filename.js", "three"},
    347                  {5, 6, "filename.js", "two"},
    348                  {6, 4, "filename.js", "one"},
    349                  {7, 2, "filename.js", nullptr}};
    350 
    351  size_t i = 0;
    352  for (JS::Handle<js::SavedFrame*> frame :
    353       js::SavedFrame::RootedRange(cx, savedFrameStack)) {
    354    CHECK(i < 4);
    355 
    356    // Line
    357    uint32_t line = 123;
    358    JS::SavedFrameResult result = JS::GetSavedFrameLine(
    359        cx, principals, frame, &line, JS::SavedFrameSelfHosted::Exclude);
    360    CHECK(result == JS::SavedFrameResult::Ok);
    361    CHECK_EQUAL(line, expected[i].line);
    362 
    363    // Column
    364    JS::TaggedColumnNumberOneOrigin column(
    365        JS::LimitedColumnNumberOneOrigin(123));
    366    result = JS::GetSavedFrameColumn(cx, principals, frame, &column,
    367                                     JS::SavedFrameSelfHosted::Exclude);
    368    CHECK(result == JS::SavedFrameResult::Ok);
    369    CHECK_EQUAL(column.oneOriginValue(), expected[i].column);
    370 
    371    // Source
    372    JS::RootedString str(cx);
    373    result = JS::GetSavedFrameSource(cx, principals, frame, &str,
    374                                     JS::SavedFrameSelfHosted::Exclude);
    375    CHECK(result == JS::SavedFrameResult::Ok);
    376    JSLinearString* linear = str->ensureLinear(cx);
    377    CHECK(linear);
    378    CHECK(js::StringEqualsAscii(linear, expected[i].source));
    379 
    380    // Function display name
    381    result = JS::GetSavedFrameFunctionDisplayName(
    382        cx, principals, frame, &str, JS::SavedFrameSelfHosted::Exclude);
    383    CHECK(result == JS::SavedFrameResult::Ok);
    384    if (auto expectedName = expected[i].functionDisplayName) {
    385      CHECK(str);
    386      linear = str->ensureLinear(cx);
    387      CHECK(linear);
    388      CHECK(js::StringEqualsAscii(linear, expectedName));
    389    } else {
    390      CHECK(!str);
    391    }
    392 
    393    i++;
    394  }
    395 
    396  return true;
    397 }
    398 END_TEST(test_GetPendingExceptionStack)