summaryrefslogtreecommitdiffstats
path: root/js/src/jsapi-tests/testErrorInterceptor.cpp
blob: 031fa85afa4b64a25d7dab20ff5dd63c9d60a517 (plain)
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
#include <iterator>

#include "js/ErrorInterceptor.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",
      "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",
      "ReferenceError: foo is not defined\0",
      "SyntaxError: expected expression, got end of script\0",
  };
  static_assert(std::size(SAMPLES) == std::size(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 < std::size(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<JSLinearString*> linear(cx, buffer.finishString());
    CHECK(equalStrings(cx, linear, gLatestMessage));

    // Cleanup.
    gLatestMessage = nullptr;
  }

  // Test again without callback.
  JS_SetErrorInterceptorCallback(cx->runtime(), nullptr);
  for (size_t i = 0; i < std::size(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<JSLinearString*> linear(cx, buffer.finishString());
    CHECK(js::StringEqualsAscii(linear, TO_STRING[i]));

    // Cleanup.
    gLatestMessage = nullptr;
  }

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

  return true;
}
END_TEST(testErrorInterceptor)