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 const double EXN_VALUE = 3.14;
11
12 static JS::PersistentRootedString gLatestMessage;
13
14 // An interceptor that stores the error in `gLatestMessage`.
15 struct SimpleInterceptor : JSErrorInterceptor {
interceptError__anonf92105970111::SimpleInterceptor16 virtual void interceptError(JSContext* cx, const JS::Value& val) override {
17 js::StringBuffer buffer(cx);
18 if (!ValueToStringBuffer(cx, val, buffer))
19 MOZ_CRASH("Could not convert to string buffer");
20 gLatestMessage = buffer.finishString();
21 if (!gLatestMessage) MOZ_CRASH("Could not convert to string");
22 }
23 };
24
equalStrings(JSContext * cx,JSString * a,JSString * b)25 bool equalStrings(JSContext* cx, JSString* a, JSString* b) {
26 int32_t result = 0;
27 if (!JS_CompareStrings(cx, a, b, &result))
28 MOZ_CRASH("Could not compare strings");
29 return result == 0;
30 }
31 } // namespace
32
BEGIN_TEST(testErrorInterceptor)33 BEGIN_TEST(testErrorInterceptor) {
34 // Run the following snippets.
35 const char* SAMPLES[] = {
36 "throw new Error('I am an Error')\0",
37 "throw new TypeError('I am a TypeError')\0",
38 "throw new ReferenceError('I am a ReferenceError')\0",
39 "throw new SyntaxError('I am a SyntaxError')\0",
40 "throw 5\0",
41 "undefined[0]\0",
42 "foo[0]\0",
43 "b[\0",
44 };
45 // With the simpleInterceptor, we should end up with the following error:
46 const char* TO_STRING[] = {
47 "Error: I am an Error\0",
48 "TypeError: I am a TypeError\0",
49 "ReferenceError: I am a ReferenceError\0",
50 "SyntaxError: I am a SyntaxError\0",
51 "5\0",
52 "TypeError: undefined has no properties\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 CHECK(JS_IsExceptionPending(cx));
70 CHECK(gLatestMessage == nullptr);
71 JS_ClearPendingException(cx);
72 }
73
74 // Test with callback.
75 SimpleInterceptor simpleInterceptor;
76 JS_SetErrorInterceptorCallback(cx->runtime(), &simpleInterceptor);
77
78 // Test that we return the right callback.
79 CHECK_EQUAL(JS_GetErrorInterceptorCallback(cx->runtime()),
80 &simpleInterceptor);
81
82 // This shouldn't cause any error.
83 EXEC("function bar() {}");
84 CHECK(gLatestMessage == nullptr);
85
86 // Test error throwing with a callback that succeeds.
87 for (size_t i = 0; i < mozilla::ArrayLength(SAMPLES); ++i) {
88 // This should cause the appropriate error.
89 if (execDontReport(SAMPLES[i], __FILE__, __LINE__))
90 MOZ_CRASH("This sample should have failed");
91 CHECK(JS_IsExceptionPending(cx));
92
93 // Check result of callback.
94 CHECK(gLatestMessage != nullptr);
95 CHECK(js::StringEqualsAscii(&gLatestMessage->asLinear(), TO_STRING[i]));
96
97 // Check the final error.
98 JS::RootedValue exn(cx);
99 CHECK(JS_GetPendingException(cx, &exn));
100 JS_ClearPendingException(cx);
101
102 js::StringBuffer buffer(cx);
103 CHECK(ValueToStringBuffer(cx, exn, buffer));
104 JS::Rooted<JSFlatString*> flat(cx, buffer.finishString());
105 CHECK(equalStrings(cx, flat, gLatestMessage));
106
107 // Cleanup.
108 gLatestMessage = nullptr;
109 }
110
111 // Test again without callback.
112 JS_SetErrorInterceptorCallback(cx->runtime(), nullptr);
113 for (size_t i = 0; i < mozilla::ArrayLength(SAMPLES); ++i) {
114 if (execDontReport(SAMPLES[i], __FILE__, __LINE__))
115 MOZ_CRASH("This sample should have failed");
116 CHECK(JS_IsExceptionPending(cx));
117
118 // Check that the callback wasn't called.
119 CHECK(gLatestMessage == nullptr);
120
121 // Check the final error.
122 JS::RootedValue exn(cx);
123 CHECK(JS_GetPendingException(cx, &exn));
124 JS_ClearPendingException(cx);
125
126 js::StringBuffer buffer(cx);
127 CHECK(ValueToStringBuffer(cx, exn, buffer));
128 JS::Rooted<JSFlatString*> flat(cx, buffer.finishString());
129 CHECK(js::StringEqualsAscii(flat, TO_STRING[i]));
130
131 // Cleanup.
132 gLatestMessage = nullptr;
133 }
134
135 // Cleanup
136 JS_SetErrorInterceptorCallback(cx->runtime(), original);
137 gLatestMessage = nullptr;
138 JS_ClearPendingException(cx);
139
140 return true;
141 }
142 END_TEST(testErrorInterceptor)
143