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