tor-browser

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

tests.h (19843B)


      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 #ifndef jsapi_tests_tests_h
      8 #define jsapi_tests_tests_h
      9 
     10 #include "mozilla/Sprintf.h"
     11 
     12 #include <errno.h>
     13 #include <stdio.h>
     14 #include <stdlib.h>
     15 #include <string.h>
     16 #include <type_traits>
     17 
     18 #include "jsapi.h"
     19 
     20 #include "gc/GC.h"
     21 #include "js/AllocPolicy.h"
     22 #include "js/ArrayBuffer.h"
     23 #include "js/CharacterEncoding.h"
     24 #include "js/Conversions.h"
     25 #include "js/Equality.h"      // JS::SameValue
     26 #include "js/GlobalObject.h"  // JS::DefaultGlobalClassOps
     27 #include "js/RegExpFlags.h"   // JS::RegExpFlags
     28 #include "js/Vector.h"
     29 #include "js/Warnings.h"  // JS::SetWarningReporter
     30 #include "vm/JSContext.h"
     31 
     32 /* Note: Aborts on OOM. */
     33 class JSAPITestString {
     34  js::Vector<char, 0, js::SystemAllocPolicy> chars;
     35 
     36 public:
     37  JSAPITestString() {}
     38  explicit JSAPITestString(const char* s) { *this += s; }
     39  JSAPITestString(const JSAPITestString& s) { *this += s; }
     40 
     41  const char* begin() const { return chars.begin(); }
     42  const char* end() const { return chars.end(); }
     43  size_t length() const { return chars.length(); }
     44  void clear() { chars.clearAndFree(); }
     45 
     46  JSAPITestString& operator+=(const char* s) {
     47    if (!chars.append(s, strlen(s))) {
     48      abort();
     49    }
     50    return *this;
     51  }
     52 
     53  JSAPITestString& operator+=(const JSAPITestString& s) {
     54    if (!chars.append(s.begin(), s.length())) {
     55      abort();
     56    }
     57    return *this;
     58  }
     59 };
     60 
     61 inline JSAPITestString operator+(const JSAPITestString& a, const char* b) {
     62  JSAPITestString result = a;
     63  result += b;
     64  return result;
     65 }
     66 
     67 inline JSAPITestString operator+(const JSAPITestString& a,
     68                                 const JSAPITestString& b) {
     69  JSAPITestString result = a;
     70  result += b;
     71  return result;
     72 }
     73 
     74 // A singly linked list of tests which doesn't require runtime intialization.
     75 //
     76 // We rely on the runtime initialization of global variables containing test
     77 // instances to add themselves to these lists, which depends on them already
     78 // being in a valid state.
     79 template <typename T>
     80 class JSAPITestList {
     81  T* first = nullptr;
     82  T* last = nullptr;
     83 
     84 public:
     85  T* getFirst() const { return first; }
     86 
     87  void pushBack(T* element) {
     88    MOZ_ASSERT(!element->next);
     89    MOZ_ASSERT(bool(first) == bool(last));
     90 
     91    if (!first) {
     92      first = element;
     93      last = element;
     94      return;
     95    }
     96 
     97    last->next = element;
     98    last = element;
     99  }
    100 };
    101 
    102 class JSAPIRuntimeTest;
    103 
    104 class JSAPITest {
    105 public:
    106  bool knownFail;
    107  JSAPITestString msgs;
    108 
    109  JSAPITest() : knownFail(false) {}
    110 
    111  virtual ~JSAPITest() {}
    112 
    113  virtual const char* name() = 0;
    114 
    115  virtual void maybeAppendException(JSAPITestString& message) {}
    116 
    117  bool fail(const JSAPITestString& msg = JSAPITestString(),
    118            const char* filename = "-", int lineno = 0) {
    119    char location[256];
    120    SprintfLiteral(location, "%s:%d:", filename, lineno);
    121 
    122    JSAPITestString message(location);
    123    message += msg;
    124 
    125    maybeAppendException(message);
    126 
    127    fprintf(stderr, "%.*s\n", int(message.length()), message.begin());
    128 
    129    if (msgs.length() != 0) {
    130      msgs += " | ";
    131    }
    132    msgs += message;
    133    return false;
    134  }
    135 
    136  JSAPITestString messages() const { return msgs; }
    137 };
    138 
    139 class JSAPIRuntimeTest : public JSAPITest {
    140 public:
    141  static JSAPITestList<JSAPIRuntimeTest> list;
    142  JSAPIRuntimeTest* next = nullptr;
    143 
    144  JSContext* cx;
    145  JS::PersistentRootedObject global;
    146 
    147  // Whether this test is willing to skip its init() and reuse a global (and
    148  // JSContext etc.) from a previous test that also has reuseGlobal=true. It
    149  // also means this test is willing to skip its uninit() if it is followed by
    150  // another reuseGlobal test.
    151  bool reuseGlobal;
    152 
    153  JSAPIRuntimeTest() : JSAPITest(), cx(nullptr), reuseGlobal(false) {
    154    list.pushBack(this);
    155  }
    156 
    157  virtual ~JSAPIRuntimeTest() {
    158    MOZ_RELEASE_ASSERT(!cx);
    159    MOZ_RELEASE_ASSERT(!global);
    160  }
    161 
    162  // Initialize this test, possibly with the cx from a previously run test.
    163  bool init(JSContext* maybeReusedContext);
    164 
    165  // If this test is ok with its cx and global being reused, release this
    166  // test's cx to be reused by another test.
    167  JSContext* maybeForgetContext();
    168 
    169  static void MaybeFreeContext(JSContext* maybeCx);
    170 
    171  // The real initialization happens in init(JSContext*), above, but this
    172  // method may be overridden to perform additional initialization after the
    173  // JSContext and global have been created.
    174  virtual bool init() { return true; }
    175  virtual void uninit();
    176 
    177  virtual bool run(JS::HandleObject global) = 0;
    178 
    179 #define EXEC(s)                                     \
    180  do {                                              \
    181    if (!exec(s, __FILE__, __LINE__)) return false; \
    182  } while (false)
    183 
    184  bool exec(const char* utf8, const char* filename, int lineno);
    185 
    186  // Like exec(), but doesn't call fail() if JS::Evaluate returns false.
    187  bool execDontReport(const char* utf8, const char* filename, int lineno);
    188 
    189 #define EVAL(s, vp)                                         \
    190  do {                                                      \
    191    if (!evaluate(s, __FILE__, __LINE__, vp)) return false; \
    192  } while (false)
    193 
    194  bool evaluate(const char* utf8, const char* filename, int lineno,
    195                JS::MutableHandleValue vp);
    196 
    197  JSAPITestString jsvalToSource(JS::HandleValue v) {
    198    JS::Rooted<JSString*> str(cx, JS_ValueToSource(cx, v));
    199    if (str) {
    200      if (JS::UniqueChars bytes = JS_EncodeStringToUTF8(cx, str)) {
    201        return JSAPITestString(bytes.get());
    202      }
    203    }
    204    JS_ClearPendingException(cx);
    205    return JSAPITestString("<<error converting value to string>>");
    206  }
    207 
    208  JSAPITestString toSource(char c) {
    209    char buf[2] = {c, '\0'};
    210    return JSAPITestString(buf);
    211  }
    212 
    213  JSAPITestString toSource(long v) {
    214    char buf[40];
    215    SprintfLiteral(buf, "%ld", v);
    216    return JSAPITestString(buf);
    217  }
    218 
    219  JSAPITestString toSource(unsigned long v) {
    220    char buf[40];
    221    SprintfLiteral(buf, "%lu", v);
    222    return JSAPITestString(buf);
    223  }
    224 
    225  JSAPITestString toSource(long long v) {
    226    char buf[40];
    227    SprintfLiteral(buf, "%lld", v);
    228    return JSAPITestString(buf);
    229  }
    230 
    231  JSAPITestString toSource(unsigned long long v) {
    232    char buf[40];
    233    SprintfLiteral(buf, "%llu", v);
    234    return JSAPITestString(buf);
    235  }
    236 
    237  JSAPITestString toSource(double d) {
    238    char buf[40];
    239    SprintfLiteral(buf, "%17lg", d);
    240    return JSAPITestString(buf);
    241  }
    242 
    243  JSAPITestString toSource(unsigned int v) {
    244    return toSource((unsigned long)v);
    245  }
    246 
    247  JSAPITestString toSource(int v) { return toSource((long)v); }
    248 
    249  JSAPITestString toSource(bool v) {
    250    return JSAPITestString(v ? "true" : "false");
    251  }
    252 
    253  JSAPITestString toSource(JS::RegExpFlags flags) {
    254    JSAPITestString str;
    255    if (flags.hasIndices()) {
    256      str += "d";
    257    }
    258    if (flags.global()) {
    259      str += "g";
    260    }
    261    if (flags.ignoreCase()) {
    262      str += "i";
    263    }
    264    if (flags.multiline()) {
    265      str += "m";
    266    }
    267    if (flags.dotAll()) {
    268      str += "s";
    269    }
    270    if (flags.unicode()) {
    271      str += "u";
    272    }
    273    if (flags.unicodeSets()) {
    274      str += "v";
    275    }
    276    if (flags.sticky()) {
    277      str += "y";
    278    }
    279    return str;
    280  }
    281 
    282  JSAPITestString toSource(JSAtom* v) {
    283    JS::RootedValue val(cx, JS::StringValue((JSString*)v));
    284    return jsvalToSource(val);
    285  }
    286 
    287  // Note that in some still-supported GCC versions (we think anything before
    288  // GCC 4.6), this template does not work when the second argument is
    289  // nullptr. It infers type U = long int. Use CHECK_NULL instead.
    290  template <typename T, typename U>
    291  bool checkEqual(const T& actual, const U& expected, const char* actualExpr,
    292                  const char* expectedExpr, const char* filename, int lineno) {
    293    static_assert(std::is_signed_v<T> == std::is_signed_v<U>,
    294                  "using CHECK_EQUAL with different-signed inputs triggers "
    295                  "compiler warnings");
    296    static_assert(
    297        std::is_unsigned_v<T> == std::is_unsigned_v<U>,
    298        "using CHECK_EQUAL with different-signed inputs triggers compiler "
    299        "warnings");
    300    return (actual == expected) ||
    301           fail(JSAPITestString("CHECK_EQUAL failed: expected (") +
    302                    expectedExpr + ") = " + toSource(expected) + ", got (" +
    303                    actualExpr + ") = " + toSource(actual),
    304                filename, lineno);
    305  }
    306 
    307 #define CHECK_EQUAL(actual, expected)                                          \
    308  do {                                                                         \
    309    if (!checkEqual(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
    310      return false;                                                            \
    311  } while (false)
    312 
    313  template <typename T>
    314  bool checkNull(const T* actual, const char* actualExpr, const char* filename,
    315                 int lineno) {
    316    return (actual == nullptr) ||
    317           fail(JSAPITestString("CHECK_NULL failed: expected nullptr, got (") +
    318                    actualExpr + ") = " + toSource(actual),
    319                filename, lineno);
    320  }
    321 
    322 #define CHECK_NULL(actual)                                             \
    323  do {                                                                 \
    324    if (!checkNull(actual, #actual, __FILE__, __LINE__)) return false; \
    325  } while (false)
    326 
    327  bool checkSame(const JS::Value& actualArg, const JS::Value& expectedArg,
    328                 const char* actualExpr, const char* expectedExpr,
    329                 const char* filename, int lineno) {
    330    bool same;
    331    JS::RootedValue actual(cx, actualArg), expected(cx, expectedArg);
    332    return (JS::SameValue(cx, actual, expected, &same) && same) ||
    333           fail(JSAPITestString(
    334                    "CHECK_SAME failed: expected JS::SameValue(cx, ") +
    335                    actualExpr + ", " + expectedExpr +
    336                    "), got !JS::SameValue(cx, " + jsvalToSource(actual) +
    337                    ", " + jsvalToSource(expected) + ")",
    338                filename, lineno);
    339  }
    340 
    341 #define CHECK_SAME(actual, expected)                                          \
    342  do {                                                                        \
    343    if (!checkSame(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
    344      return false;                                                           \
    345  } while (false)
    346 
    347 #define CHECK(expr)                                                  \
    348  do {                                                               \
    349    if (!(expr))                                                     \
    350      return fail(JSAPITestString("CHECK failed: " #expr), __FILE__, \
    351                  __LINE__);                                         \
    352  } while (false)
    353 
    354  void maybeAppendException(JSAPITestString& message) override {
    355    if (JS_IsExceptionPending(cx)) {
    356      message += " -- ";
    357 
    358      js::gc::AutoSuppressGC gcoff(cx);
    359      JS::RootedValue v(cx);
    360      JS_GetPendingException(cx, &v);
    361      JS_ClearPendingException(cx);
    362      JS::Rooted<JSString*> s(cx, JS::ToString(cx, v));
    363      if (s) {
    364        if (JS::UniqueChars bytes = JS_EncodeStringToLatin1(cx, s)) {
    365          message += bytes.get();
    366        }
    367      }
    368    }
    369  }
    370 
    371  static const JSClass* basicGlobalClass() {
    372    static const JSClass c = {
    373        "global",
    374        JSCLASS_GLOBAL_FLAGS,
    375        &JS::DefaultGlobalClassOps,
    376    };
    377    return &c;
    378  }
    379 
    380 protected:
    381  static bool print(JSContext* cx, unsigned argc, JS::Value* vp) {
    382    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
    383 
    384    JS::Rooted<JSString*> str(cx);
    385    for (unsigned i = 0; i < args.length(); i++) {
    386      str = JS::ToString(cx, args[i]);
    387      if (!str) {
    388        return false;
    389      }
    390      JS::UniqueChars bytes = JS_EncodeStringToUTF8(cx, str);
    391      if (!bytes) {
    392        return false;
    393      }
    394      printf("%s%s", i ? " " : "", bytes.get());
    395    }
    396 
    397    putchar('\n');
    398    fflush(stdout);
    399    args.rval().setUndefined();
    400    return true;
    401  }
    402 
    403  bool definePrint();
    404 
    405  virtual JSContext* createContext() {
    406    JSContext* cx = JS_NewContext(8L * 1024 * 1024);
    407    if (!cx) {
    408      return nullptr;
    409    }
    410    JS::SetWarningReporter(cx, &reportWarning);
    411    return cx;
    412  }
    413 
    414  static void reportWarning(JSContext* cx, JSErrorReport* report) {
    415    MOZ_RELEASE_ASSERT(report->isWarning());
    416 
    417    fprintf(stderr, "%s:%u:%s\n",
    418            report->filename ? report->filename.c_str() : "<no filename>",
    419            (unsigned int)report->lineno, report->message().c_str());
    420  }
    421 
    422  virtual const JSClass* getGlobalClass() { return basicGlobalClass(); }
    423 
    424  virtual JSObject* createGlobal(JSPrincipals* principals = nullptr);
    425 };
    426 
    427 class JSAPIFrontendTest : public JSAPITest {
    428 public:
    429  static JSAPITestList<JSAPIFrontendTest> list;
    430  JSAPIFrontendTest* next = nullptr;
    431 
    432  JSAPIFrontendTest() : JSAPITest() { list.pushBack(this); }
    433 
    434  virtual ~JSAPIFrontendTest() {}
    435 
    436  virtual bool init() { return true; }
    437  virtual void uninit() {}
    438 
    439  virtual bool run() = 0;
    440 };
    441 
    442 #define BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, extra) \
    443  class cls_##testname : public JSAPIRuntimeTest {                   \
    444   public:                                                           \
    445    virtual const char* name() override { return #testname; }        \
    446    extra virtual bool run(JS::HandleObject global) override attrs
    447 
    448 #define BEGIN_TEST_WITH_ATTRIBUTES(testname, attrs) \
    449  BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, )
    450 
    451 #define BEGIN_TEST(testname) BEGIN_TEST_WITH_ATTRIBUTES(testname, )
    452 
    453 #define BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, extra) \
    454  class cls_##testname : public JSAPIFrontendTest {                           \
    455   public:                                                                    \
    456    virtual const char* name() override { return #testname; }                 \
    457    extra virtual bool run() override attrs
    458 
    459 #define BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES(testname, attrs) \
    460  BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES_AND_EXTRA(testname, attrs, )
    461 
    462 #define BEGIN_FRONTEND_TEST(testname) \
    463  BEGIN_FRONTEND_TEST_WITH_ATTRIBUTES(testname, )
    464 
    465 #define BEGIN_REUSABLE_TEST(testname)   \
    466  BEGIN_TEST_WITH_ATTRIBUTES_AND_EXTRA( \
    467      testname, ,                       \
    468      cls_##testname() : JSAPIRuntimeTest() { reuseGlobal = true; })
    469 
    470 #define END_TEST(testname) \
    471  }                        \
    472  ;                        \
    473  MOZ_RUNINIT static cls_##testname cls_##testname##_instance;
    474 
    475 /*
    476 * A "fixture" is a subclass of JSAPIRuntimeTest that holds common definitions
    477 * for a set of tests. Each test that wants to use the fixture should use
    478 * BEGIN_FIXTURE_TEST and END_FIXTURE_TEST, just as one would use BEGIN_TEST and
    479 * END_TEST, but include the fixture class as the first argument. The fixture
    480 * class's declarations are then in scope for the test bodies.
    481 */
    482 
    483 #define BEGIN_FIXTURE_TEST(fixture, testname)                 \
    484  class cls_##testname : public fixture {                     \
    485   public:                                                    \
    486    virtual const char* name() override { return #testname; } \
    487    virtual bool run(JS::HandleObject global) override
    488 
    489 #define END_FIXTURE_TEST(fixture, testname) \
    490  }                                         \
    491  ;                                         \
    492  MOZ_RUNINIT static cls_##testname cls_##testname##_instance;
    493 
    494 /*
    495 * A class for creating and managing one temporary file.
    496 *
    497 * We could use the ISO C temporary file functions here, but those try to
    498 * create files in the root directory on Windows, which fails for users
    499 * without Administrator privileges.
    500 */
    501 class TempFile {
    502  const char* name;
    503  FILE* stream;
    504 
    505 public:
    506  TempFile() : name(), stream() {}
    507  ~TempFile() {
    508    if (stream) {
    509      close();
    510    }
    511    if (name) {
    512      remove();
    513    }
    514  }
    515 
    516  /*
    517   * Return a stream for a temporary file named |fileName|. Infallible.
    518   * Use only once per TempFile instance. If the file is not explicitly
    519   * closed and deleted via the member functions below, this object's
    520   * destructor will clean them up.
    521   */
    522  FILE* open(const char* fileName) {
    523    stream = fopen(fileName, "wb+");
    524    if (!stream) {
    525      fprintf(stderr, "error opening temporary file '%s': %s\n", fileName,
    526              strerror(errno));
    527      exit(1);
    528    }
    529    name = fileName;
    530    return stream;
    531  }
    532 
    533  /* Close the temporary file's stream. */
    534  void close() {
    535    if (fclose(stream) == EOF) {
    536      fprintf(stderr, "error closing temporary file '%s': %s\n", name,
    537              strerror(errno));
    538      exit(1);
    539    }
    540    stream = nullptr;
    541  }
    542 
    543  /* Delete the temporary file. */
    544  void remove() {
    545    if (::remove(name) != 0) {
    546      fprintf(stderr, "error deleting temporary file '%s': %s\n", name,
    547              strerror(errno));
    548      exit(1);
    549    }
    550    name = nullptr;
    551  }
    552 };
    553 
    554 // Just a wrapper around JSPrincipals that allows static construction.
    555 class TestJSPrincipals : public JSPrincipals {
    556 public:
    557  explicit TestJSPrincipals(int rc = 0) : JSPrincipals() { refcount = rc; }
    558 
    559  bool write(JSContext* cx, JSStructuredCloneWriter* writer) override {
    560    MOZ_ASSERT(false, "not implemented");
    561    return false;
    562  }
    563 
    564  bool isSystemOrAddonPrincipal() override { return true; }
    565 };
    566 
    567 // A class that simulates externally memory-managed data, for testing with
    568 // array buffers.
    569 class ExternalData {
    570  char* contents_;
    571  size_t len_;
    572  bool uniquePointerCreated_ = false;
    573 
    574 public:
    575  explicit ExternalData(const char* str)
    576      : contents_(strdup(str)), len_(strlen(str) + 1) {}
    577 
    578  size_t len() const { return len_; }
    579  void* contents() const { return contents_; }
    580  char* asString() const { return contents_; }
    581  bool wasFreed() const { return !contents_; }
    582 
    583  void free() {
    584    MOZ_ASSERT(!wasFreed());
    585    ::free(contents_);
    586    contents_ = nullptr;
    587  }
    588 
    589  mozilla::UniquePtr<void, JS::BufferContentsDeleter> pointer() {
    590    MOZ_ASSERT(!uniquePointerCreated_,
    591               "Not allowed to create multiple unique pointers to contents");
    592    uniquePointerCreated_ = true;
    593    return {contents_, {ExternalData::freeCallback, this}};
    594  }
    595 
    596  static void freeCallback(void* contents, void* userData) {
    597    auto self = static_cast<ExternalData*>(userData);
    598    MOZ_ASSERT(self->contents() == contents);
    599    self->free();
    600  }
    601 };
    602 
    603 class AutoGCParameter {
    604  JSContext* cx_;
    605  JSGCParamKey key_;
    606  uint32_t value_;
    607 
    608 public:
    609  explicit AutoGCParameter(JSContext* cx, JSGCParamKey key, uint32_t value)
    610      : cx_(cx), key_(key), value_() {
    611    value_ = JS_GetGCParameter(cx, key);
    612    JS_SetGCParameter(cx, key, value);
    613  }
    614  ~AutoGCParameter() { JS_SetGCParameter(cx_, key_, value_); }
    615 };
    616 
    617 #ifdef JS_GC_ZEAL
    618 /*
    619 * Temporarily disable the GC zeal setting. This is only useful in tests that
    620 * need very explicit GC behavior and should not be used elsewhere.
    621 */
    622 class AutoLeaveZeal {
    623  JSContext* cx_;
    624  uint32_t zealBits_;
    625  uint32_t frequency_;
    626 
    627 public:
    628  explicit AutoLeaveZeal(JSContext* cx) : cx_(cx), zealBits_(0), frequency_(0) {
    629    uint32_t dummy;
    630    JS::GetGCZealBits(cx_, &zealBits_, &frequency_, &dummy);
    631    JS::SetGCZeal(cx_, 0, 0);
    632    JS::PrepareForFullGC(cx_);
    633    JS::NonIncrementalGC(cx_, JS::GCOptions::Normal, JS::GCReason::DEBUG_GC);
    634  }
    635  ~AutoLeaveZeal() {
    636    JS::SetGCZeal(cx_, 0, 0);
    637    for (size_t i = 0; i < sizeof(zealBits_) * 8; i++) {
    638      if (zealBits_ & (1 << i)) {
    639        JS::SetGCZeal(cx_, i, frequency_);
    640      }
    641    }
    642 
    643 #  ifdef DEBUG
    644    uint32_t zealBitsAfter, frequencyAfter, dummy;
    645    JS::GetGCZealBits(cx_, &zealBitsAfter, &frequencyAfter, &dummy);
    646    MOZ_ASSERT(zealBitsAfter == zealBits_);
    647    MOZ_ASSERT(frequencyAfter == frequency_);
    648 #  endif
    649  }
    650 };
    651 
    652 #else
    653 class AutoLeaveZeal {
    654 public:
    655  explicit AutoLeaveZeal(JSContext* cx) {}
    656 };
    657 #endif
    658 
    659 #endif /* jsapi_tests_tests_h */