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__anon62923cc70111::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