1 #include <iterator>
2
3 #include "jsapi.h"
4
5 #include "jsapi-tests/tests.h"
6
7 #include "util/StringBuffer.h"
8
9 // Tests for JS_GetErrorInterceptorCallback and JS_SetErrorInterceptorCallback.
10
11 namespace {
12 static JS::PersistentRootedString gLatestMessage;
13
14 // An interceptor that stores the error in `gLatestMessage`.
15 struct SimpleInterceptor : JSErrorInterceptor {
interceptError__anonb87fd3de0111::SimpleInterceptor16 virtual void interceptError(JSContext* cx, JS::HandleValue val) override {
17 js::JSStringBuilder buffer(cx);
18 if (!ValueToStringBuffer(cx, val, buffer)) {
19 MOZ_CRASH("Could not convert to string buffer");
20 }
21 gLatestMessage = buffer.finishString();
22 if (!gLatestMessage) {
23 MOZ_CRASH("Could not convert to string");
24 }
25 }
26 };
27
equalStrings(JSContext * cx,JSString * a,JSString * b)28 bool equalStrings(JSContext* cx, JSString* a, JSString* b) {
29 int32_t result = 0;
30 if (!JS_CompareStrings(cx, a, b, &result)) {
31 MOZ_CRASH("Could not compare strings");
32 }
33 return result == 0;
34 }
35 } // namespace
36
BEGIN_TEST(testErrorInterceptor)37 BEGIN_TEST(testErrorInterceptor) {
38 // Run the following snippets.
39 const char* SAMPLES[] = {
40 "throw new Error('I am an Error')\0",
41 "throw new TypeError('I am a TypeError')\0",
42 "throw new ReferenceError('I am a ReferenceError')\0",
43 "throw new SyntaxError('I am a SyntaxError')\0",
44 "throw 5\0",
45 "foo[0]\0",
46 "b[\0",
47 };
48 // With the simpleInterceptor, we should end up with the following error:
49 const char* TO_STRING[] = {
50 "Error: I am an Error\0",
51 "TypeError: I am a TypeError\0",
52 "ReferenceError: I am a ReferenceError\0",
53 "SyntaxError: I am a SyntaxError\0",
54 "5\0",
55 "ReferenceError: foo is not defined\0",
56 "SyntaxError: expected expression, got end of script\0",
57 };
58 static_assert(std::size(SAMPLES) == std::size(TO_STRING));
59
60 // Save original callback.
61 JSErrorInterceptor* original = JS_GetErrorInterceptorCallback(cx->runtime());
62 gLatestMessage.init(cx);
63
64 // Test without callback.
65 JS_SetErrorInterceptorCallback(cx->runtime(), nullptr);
66 CHECK(gLatestMessage == nullptr);
67
68 for (auto sample : SAMPLES) {
69 if (execDontReport(sample, __FILE__, __LINE__)) {
70 MOZ_CRASH("This sample should have failed");
71 }
72 CHECK(JS_IsExceptionPending(cx));
73 CHECK(gLatestMessage == nullptr);
74 JS_ClearPendingException(cx);
75 }
76
77 // Test with callback.
78 SimpleInterceptor simpleInterceptor;
79 JS_SetErrorInterceptorCallback(cx->runtime(), &simpleInterceptor);
80
81 // Test that we return the right callback.
82 CHECK_EQUAL(JS_GetErrorInterceptorCallback(cx->runtime()),
83 &simpleInterceptor);
84
85 // This shouldn't cause any error.
86 EXEC("function bar() {}");
87 CHECK(gLatestMessage == nullptr);
88
89 // Test error throwing with a callback that succeeds.
90 for (size_t i = 0; i < std::size(SAMPLES); ++i) {
91 // This should cause the appropriate error.
92 if (execDontReport(SAMPLES[i], __FILE__, __LINE__)) {
93 MOZ_CRASH("This sample should have failed");
94 }
95 CHECK(JS_IsExceptionPending(cx));
96
97 // Check result of callback.
98 CHECK(gLatestMessage != nullptr);
99 CHECK(js::StringEqualsAscii(&gLatestMessage->asLinear(), TO_STRING[i]));
100
101 // Check the final error.
102 JS::RootedValue exn(cx);
103 CHECK(JS_GetPendingException(cx, &exn));
104 JS_ClearPendingException(cx);
105
106 js::JSStringBuilder buffer(cx);
107 CHECK(ValueToStringBuffer(cx, exn, buffer));
108 JS::Rooted<JSLinearString*> linear(cx, buffer.finishString());
109 CHECK(equalStrings(cx, linear, gLatestMessage));
110
111 // Cleanup.
112 gLatestMessage = nullptr;
113 }
114
115 // Test again without callback.
116 JS_SetErrorInterceptorCallback(cx->runtime(), nullptr);
117 for (size_t i = 0; i < std::size(SAMPLES); ++i) {
118 if (execDontReport(SAMPLES[i], __FILE__, __LINE__)) {
119 MOZ_CRASH("This sample should have failed");
120 }
121 CHECK(JS_IsExceptionPending(cx));
122
123 // Check that the callback wasn't called.
124 CHECK(gLatestMessage == nullptr);
125
126 // Check the final error.
127 JS::RootedValue exn(cx);
128 CHECK(JS_GetPendingException(cx, &exn));
129 JS_ClearPendingException(cx);
130
131 js::JSStringBuilder buffer(cx);
132 CHECK(ValueToStringBuffer(cx, exn, buffer));
133 JS::Rooted<JSLinearString*> linear(cx, buffer.finishString());
134 CHECK(js::StringEqualsAscii(linear, TO_STRING[i]));
135
136 // Cleanup.
137 gLatestMessage = nullptr;
138 }
139
140 // Cleanup
141 JS_SetErrorInterceptorCallback(cx->runtime(), original);
142 gLatestMessage = nullptr;
143 JS_ClearPendingException(cx);
144
145 return true;
146 }
147 END_TEST(testErrorInterceptor)
148