Skip to content

Commit

Permalink
Propagate Load Errors through JSExecutor API
Browse files Browse the repository at this point in the history
Reviewed By: mhorowitz

Differential Revision: D4315204

fbshipit-source-id: ae282e0a0398a602dd790de3a2b1adb5fdc1f50c
  • Loading branch information
Ashok Menon authored and facebook-github-bot committed Dec 17, 2016
1 parent 9f3ef48 commit 6ca6b49
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 18 deletions.
1 change: 1 addition & 0 deletions ReactCommon/cxxreact/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ CXXREACT_PUBLIC_HEADERS = [
'NativeModule.h',
'NativeToJsBridge.h',
'Platform.h',
'RecoverableError.h',
'SystraceSection.h',
]

Expand Down
64 changes: 46 additions & 18 deletions ReactCommon/cxxreact/JSCExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <folly/Conv.h>
#include <fcntl.h>
#include <sys/time.h>
#include <system_error>

#include <jschelpers/JSCHelpers.h>
#include <jschelpers/Value.h>
Expand All @@ -30,6 +31,7 @@
#include "JSCUtils.h"
#include "JSModulesUnbundle.h"
#include "ModuleRegistry.h"
#include "RecoverableError.h"

#if defined(WITH_JSC_EXTRA_TRACING) || DEBUG
#include "JSCTracing.h"
Expand Down Expand Up @@ -60,6 +62,8 @@
namespace facebook {
namespace react {

using namespace detail;

namespace {

template<JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])>
Expand Down Expand Up @@ -310,6 +314,30 @@ void JSCExecutor::terminateOnJSVMThread() {
m_context = nullptr;
}

#ifdef WITH_FBJSCEXTENSIONS
static const char* explainLoadSourceError(JSLoadSourceError err) {
switch (err) {
case JSLoadSourceErrorNone:
return "No error encountered during source load";

case JSLoadSourceErrorOnRead:
return "Error reading source";

case JSLoadSourceErrorNotCompiled:
return "Source is not compiled";

case JSLoadSourceErrorVersionMismatch:
return "Source version not supported";

case JSLoadSourceErrorUnknown:
return "Unknown error occurred when loading source";

default:
return "Bad error code";
}
}
#endif

#ifdef WITH_FBJSCEXTENSIONS
void JSCExecutor::loadApplicationScript(
std::string bundlePath,
Expand All @@ -318,9 +346,9 @@ void JSCExecutor::loadApplicationScript(
SystraceSection s("JSCExecutor::loadApplicationScript",
"sourceURL", sourceURL);

folly::throwOnFail<std::runtime_error>(
(flags & UNPACKED_JS_SOURCE) || (flags & UNPACKED_BYTECODE),
"Optimized bundle with no unpacked source or bytecode");
if (!(flags & (UNPACKED_JS_SOURCE | UNPACKED_BYTECODE))) {
throw RecoverableError("Optimized bundle with no unpacked source or bytecode");
}

String jsSourceURL(m_context, sourceURL.c_str());
JSSourceCodeRef sourceCode = nullptr;
Expand All @@ -332,14 +360,17 @@ void JSCExecutor::loadApplicationScript(

if (flags & UNPACKED_BYTECODE) {
int fd = open((bundlePath + UNPACKED_BYTECODE_SUFFIX).c_str(), O_RDONLY);
folly::checkUnixError(fd, "Couldn't open compiled bundle");
RecoverableError::runRethrowingAsRecoverable<std::system_error>([fd]() {
folly::checkUnixError(fd, "Couldn't open compiled bundle");
});
SCOPE_EXIT { close(fd); };
sourceCode = JSCreateCompiledSourceCode(fd, jsSourceURL, nullptr);

folly::throwOnFail<std::runtime_error>(
sourceCode != nullptr,
"Could not create compiled source code"
);
JSLoadSourceError jsError;
sourceCode = JSCreateCompiledSourceCode(fd, jsSourceURL, &jsError);

if (!sourceCode) {
throw RecoverableError(explainLoadSourceError(jsError));
}
} else {
auto jsScriptBigString = JSBigOptimizedBundleString::fromOptimizedBundle(bundlePath);
if (!jsScriptBigString->isAscii()) {
Expand Down Expand Up @@ -382,18 +413,15 @@ void JSCExecutor::loadApplicationScript(
// Not bytecode, fall through.
return JSExecutor::loadApplicationScript(fd, sourceURL);

case JSLoadSourceErrorVersionMismatch:
throw std::runtime_error("Compiled Source Version Mismatch");

case JSLoadSourceErrorNone:
folly::throwOnFail<std::runtime_error>(
bcSourceCode != nullptr,
"Unexpected error opening compiled bundle"
);
if (!bcSourceCode) {
throw std::runtime_error("Unexpected error opening compiled bundle");
}
break;

default:
throw std::runtime_error("Unhandled Compiled Source Error");
case JSLoadSourceErrorVersionMismatch:
case JSLoadSourceErrorUnknown:
throw RecoverableError(explainLoadSourceError(jsError));
}

ReactMarker::logMarker("RUN_JS_BUNDLE_START");
Expand Down
47 changes: 47 additions & 0 deletions ReactCommon/cxxreact/RecoverableError.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2004-present Facebook. All Rights Reserved.

#pragma once

#include <exception>
#include <functional>
#include <string>

namespace facebook {
namespace react {
namespace detail {

/**
* RecoverableError
*
* An exception that it is expected we should be able to recover from.
*/
struct RecoverableError : public std::exception {

explicit RecoverableError(const std::string &what_)
: m_what {what_}
{}

virtual const char* what() const throw() override { return m_what.c_str(); }

/**
* runRethrowingAsRecoverable
*
* Helper function that converts any exception of type `E`, thrown within the
* `act` routine into a recoverable error with the same message.
*/
template <typename E>
inline static void runRethrowingAsRecoverable(std::function<void()> act) {
try {
act();
} catch(const E &err) {
throw RecoverableError(err.what());
}
}

private:
std::string m_what;
};

} // namespace detail
} // namespace react
} // namespace facebook
1 change: 1 addition & 0 deletions ReactCommon/cxxreact/tests/BUCK
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
TEST_SRCS = [
'CxxMessageQueueTest.cpp',
'RecoverableErrorTest.cpp',
'jsarg_helpers.cpp',
'jsbigstring.cpp',
'jscexecutor.cpp',
Expand Down
36 changes: 36 additions & 0 deletions ReactCommon/cxxreact/tests/RecoverableErrorTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2004-present Facebook. All Rights Reserved.

#include <gtest/gtest.h>

#include <exception>
#include <stdexcept>

#include <cxxreact/RecoverableError.h>

using namespace facebook::react::detail;

TEST(RecoverableError, RunRethrowingAsRecoverableRecoverTest) {
try {
RecoverableError::runRethrowingAsRecoverable<std::runtime_error>([]() {
throw std::runtime_error("catch me");
});
FAIL() << "Unthrown exception";
} catch (const RecoverableError &err) {
ASSERT_STREQ(err.what(), "catch me");
} catch (...) {
FAIL() << "Uncaught exception";
}
}

TEST(RecoverableError, RunRethrowingAsRecoverableFallthroughTest) {
try {
RecoverableError::runRethrowingAsRecoverable<std::runtime_error>([]() {
throw std::logic_error("catch me");
});
FAIL() << "Unthrown exception";
} catch (const RecoverableError &err) {
FAIL() << "Recovered exception that should have fallen through";
} catch (const std::exception &err) {
ASSERT_STREQ(err.what(), "catch me");
}
}

0 comments on commit 6ca6b49

Please sign in to comment.