diff options
Diffstat (limited to 'js/src/jsapi-tests/testErrorInterceptor.cpp')
-rw-r--r-- | js/src/jsapi-tests/testErrorInterceptor.cpp | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testErrorInterceptor.cpp b/js/src/jsapi-tests/testErrorInterceptor.cpp new file mode 100644 index 0000000000..031fa85afa --- /dev/null +++ b/js/src/jsapi-tests/testErrorInterceptor.cpp @@ -0,0 +1,145 @@ +#include <iterator> + +#include "js/ErrorInterceptor.h" +#include "jsapi-tests/tests.h" +#include "util/StringBuffer.h" + +// Tests for JS_GetErrorInterceptorCallback and JS_SetErrorInterceptorCallback. + +namespace { +static JS::PersistentRootedString gLatestMessage; + +// An interceptor that stores the error in `gLatestMessage`. +struct SimpleInterceptor : JSErrorInterceptor { + virtual void interceptError(JSContext* cx, JS::HandleValue val) override { + js::JSStringBuilder buffer(cx); + if (!ValueToStringBuffer(cx, val, buffer)) { + MOZ_CRASH("Could not convert to string buffer"); + } + gLatestMessage = buffer.finishString(); + if (!gLatestMessage) { + MOZ_CRASH("Could not convert to string"); + } + } +}; + +bool equalStrings(JSContext* cx, JSString* a, JSString* b) { + int32_t result = 0; + if (!JS_CompareStrings(cx, a, b, &result)) { + MOZ_CRASH("Could not compare strings"); + } + return result == 0; +} +} // namespace + +BEGIN_TEST(testErrorInterceptor) { + // Run the following snippets. + const char* SAMPLES[] = { + "throw new Error('I am an Error')\0", + "throw new TypeError('I am a TypeError')\0", + "throw new ReferenceError('I am a ReferenceError')\0", + "throw new SyntaxError('I am a SyntaxError')\0", + "throw 5\0", + "foo[0]\0", + "b[\0", + }; + // With the simpleInterceptor, we should end up with the following error: + const char* TO_STRING[] = { + "Error: I am an Error\0", + "TypeError: I am a TypeError\0", + "ReferenceError: I am a ReferenceError\0", + "SyntaxError: I am a SyntaxError\0", + "5\0", + "ReferenceError: foo is not defined\0", + "SyntaxError: expected expression, got end of script\0", + }; + static_assert(std::size(SAMPLES) == std::size(TO_STRING)); + + // Save original callback. + JSErrorInterceptor* original = JS_GetErrorInterceptorCallback(cx->runtime()); + gLatestMessage.init(cx); + + // Test without callback. + JS_SetErrorInterceptorCallback(cx->runtime(), nullptr); + CHECK(gLatestMessage == nullptr); + + for (auto sample : SAMPLES) { + if (execDontReport(sample, __FILE__, __LINE__)) { + MOZ_CRASH("This sample should have failed"); + } + CHECK(JS_IsExceptionPending(cx)); + CHECK(gLatestMessage == nullptr); + JS_ClearPendingException(cx); + } + + // Test with callback. + SimpleInterceptor simpleInterceptor; + JS_SetErrorInterceptorCallback(cx->runtime(), &simpleInterceptor); + + // Test that we return the right callback. + CHECK_EQUAL(JS_GetErrorInterceptorCallback(cx->runtime()), + &simpleInterceptor); + + // This shouldn't cause any error. + EXEC("function bar() {}"); + CHECK(gLatestMessage == nullptr); + + // Test error throwing with a callback that succeeds. + for (size_t i = 0; i < std::size(SAMPLES); ++i) { + // This should cause the appropriate error. + if (execDontReport(SAMPLES[i], __FILE__, __LINE__)) { + MOZ_CRASH("This sample should have failed"); + } + CHECK(JS_IsExceptionPending(cx)); + + // Check result of callback. + CHECK(gLatestMessage != nullptr); + CHECK(js::StringEqualsAscii(&gLatestMessage->asLinear(), TO_STRING[i])); + + // Check the final error. + JS::RootedValue exn(cx); + CHECK(JS_GetPendingException(cx, &exn)); + JS_ClearPendingException(cx); + + js::JSStringBuilder buffer(cx); + CHECK(ValueToStringBuffer(cx, exn, buffer)); + JS::Rooted<JSLinearString*> linear(cx, buffer.finishString()); + CHECK(equalStrings(cx, linear, gLatestMessage)); + + // Cleanup. + gLatestMessage = nullptr; + } + + // Test again without callback. + JS_SetErrorInterceptorCallback(cx->runtime(), nullptr); + for (size_t i = 0; i < std::size(SAMPLES); ++i) { + if (execDontReport(SAMPLES[i], __FILE__, __LINE__)) { + MOZ_CRASH("This sample should have failed"); + } + CHECK(JS_IsExceptionPending(cx)); + + // Check that the callback wasn't called. + CHECK(gLatestMessage == nullptr); + + // Check the final error. + JS::RootedValue exn(cx); + CHECK(JS_GetPendingException(cx, &exn)); + JS_ClearPendingException(cx); + + js::JSStringBuilder buffer(cx); + CHECK(ValueToStringBuffer(cx, exn, buffer)); + JS::Rooted<JSLinearString*> linear(cx, buffer.finishString()); + CHECK(js::StringEqualsAscii(linear, TO_STRING[i])); + + // Cleanup. + gLatestMessage = nullptr; + } + + // Cleanup + JS_SetErrorInterceptorCallback(cx->runtime(), original); + gLatestMessage = nullptr; + JS_ClearPendingException(cx); + + return true; +} +END_TEST(testErrorInterceptor) |