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