DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (b6d82b1a6b02)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
#include "jsapi.h"

#include "jsapi-tests/tests.h"

#include "util/StringBuffer.h"

// Tests for JS_GetErrorInterceptorCallback and JS_SetErrorInterceptorCallback.

namespace {
static JS::PersistentRootedString gLatestMessage;

// An interceptor that stores the error in `gLatestMessage`.
struct SimpleInterceptor : JSErrorInterceptor {
  virtual void interceptError(JSContext* cx, JS::HandleValue val) override {
    js::JSStringBuilder buffer(cx);
    if (!ValueToStringBuffer(cx, val, buffer)) {
      MOZ_CRASH("Could not convert to string buffer");
    }
    gLatestMessage = buffer.finishString();
    if (!gLatestMessage) {
      MOZ_CRASH("Could not convert to string");
    }
  }
};

bool equalStrings(JSContext* cx, JSString* a, JSString* b) {
  int32_t result = 0;
  if (!JS_CompareStrings(cx, a, b, &result)) {
    MOZ_CRASH("Could not compare strings");
  }
  return result == 0;
}
}  // namespace

BEGIN_TEST(testErrorInterceptor) {
  // Run the following snippets.
  const char* SAMPLES[] = {
      "throw new Error('I am an Error')\0",
      "throw new TypeError('I am a TypeError')\0",
      "throw new ReferenceError('I am a ReferenceError')\0",
      "throw new SyntaxError('I am a SyntaxError')\0",
      "throw 5\0",
      "undefined[0]\0",
      "foo[0]\0",
      "b[\0",
  };
  // With the simpleInterceptor, we should end up with the following error:
  const char* TO_STRING[] = {
      "Error: I am an Error\0",
      "TypeError: I am a TypeError\0",
      "ReferenceError: I am a ReferenceError\0",
      "SyntaxError: I am a SyntaxError\0",
      "5\0",
      "TypeError: undefined has no properties\0",
      "ReferenceError: foo is not defined\0",
      "SyntaxError: expected expression, got end of script\0",
  };
  MOZ_ASSERT(mozilla::ArrayLength(SAMPLES) == mozilla::ArrayLength(TO_STRING));

  // Save original callback.
  JSErrorInterceptor* original = JS_GetErrorInterceptorCallback(cx->runtime());
  gLatestMessage.init(cx);

  // Test without callback.
  JS_SetErrorInterceptorCallback(cx->runtime(), nullptr);
  CHECK(gLatestMessage == nullptr);

  for (auto sample : SAMPLES) {
    if (execDontReport(sample, __FILE__, __LINE__)) {
      MOZ_CRASH("This sample should have failed");
    }
    CHECK(JS_IsExceptionPending(cx));
    CHECK(gLatestMessage == nullptr);
    JS_ClearPendingException(cx);
  }

  // Test with callback.
  SimpleInterceptor simpleInterceptor;
  JS_SetErrorInterceptorCallback(cx->runtime(), &simpleInterceptor);

  // Test that we return the right callback.
  CHECK_EQUAL(JS_GetErrorInterceptorCallback(cx->runtime()),
              &simpleInterceptor);

  // This shouldn't cause any error.
  EXEC("function bar() {}");
  CHECK(gLatestMessage == nullptr);

  // Test error throwing with a callback that succeeds.
  for (size_t i = 0; i < mozilla::ArrayLength(SAMPLES); ++i) {
    // This should cause the appropriate error.
    if (execDontReport(SAMPLES[i], __FILE__, __LINE__)) {
      MOZ_CRASH("This sample should have failed");
    }
    CHECK(JS_IsExceptionPending(cx));

    // Check result of callback.
    CHECK(gLatestMessage != nullptr);
    CHECK(js::StringEqualsAscii(&gLatestMessage->asLinear(), TO_STRING[i]));

    // Check the final error.
    JS::RootedValue exn(cx);
    CHECK(JS_GetPendingException(cx, &exn));
    JS_ClearPendingException(cx);

    js::JSStringBuilder buffer(cx);
    CHECK(ValueToStringBuffer(cx, exn, buffer));
    JS::Rooted<JSFlatString*> flat(cx, buffer.finishString());
    CHECK(equalStrings(cx, flat, gLatestMessage));

    // Cleanup.
    gLatestMessage = nullptr;
  }

  // Test again without callback.
  JS_SetErrorInterceptorCallback(cx->runtime(), nullptr);
  for (size_t i = 0; i < mozilla::ArrayLength(SAMPLES); ++i) {
    if (execDontReport(SAMPLES[i], __FILE__, __LINE__)) {
      MOZ_CRASH("This sample should have failed");
    }
    CHECK(JS_IsExceptionPending(cx));

    // Check that the callback wasn't called.
    CHECK(gLatestMessage == nullptr);

    // Check the final error.
    JS::RootedValue exn(cx);
    CHECK(JS_GetPendingException(cx, &exn));
    JS_ClearPendingException(cx);

    js::JSStringBuilder buffer(cx);
    CHECK(ValueToStringBuffer(cx, exn, buffer));
    JS::Rooted<JSFlatString*> flat(cx, buffer.finishString());
    CHECK(js::StringEqualsAscii(flat, TO_STRING[i]));

    // Cleanup.
    gLatestMessage = nullptr;
  }

  // Cleanup
  JS_SetErrorInterceptorCallback(cx->runtime(), original);
  gLatestMessage = nullptr;
  JS_ClearPendingException(cx);

  return true;
}
END_TEST(testErrorInterceptor)