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