diff --git a/ReactCommon/cxxreact/Executor.h b/ReactCommon/cxxreact/Executor.h index 3f2b103a4..6f2f83fdc 100644 --- a/ReactCommon/cxxreact/Executor.h +++ b/ReactCommon/cxxreact/Executor.h @@ -148,6 +148,61 @@ private: size_t m_size; }; +// JSBigString interface implemented by a file-backed mmap region. +class JSBigFileString : public JSBigString { +public: + + JSBigFileString(int fd, size_t size, off_t offset = 0) + : m_fd {fd} + , m_data {nullptr} + { + // Offsets given to mmap must be page aligend. We abstract away that + // restriction by sending a page aligned offset to mmap, and keeping track + // of the offset within the page that we must alter the mmap pointer by to + // get the final desired offset. + auto ps = getpagesize(); + auto d = lldiv(offset, ps); + + m_mapOff = d.quot; + m_pageOff = d.rem; + m_size = size + m_pageOff; + } + + ~JSBigFileString() { + if (m_data) { + munmap((void *)m_data, m_size); + } + close(m_fd); + } + + bool isAscii() const override { + return true; + } + + const char *c_str() const override { + if (!m_data) { + m_data = (const char *)mmap(0, m_size, PROT_READ, MAP_SHARED, m_fd, m_mapOff); + CHECK(m_data != MAP_FAILED); + } + return m_data + m_pageOff; + } + + size_t size() const override { + return m_size - m_pageOff; + } + + int fd() const { + return m_fd; + } + +private: + int m_fd; // The file descriptor being mmaped + size_t m_size; // The size of the mmaped region + size_t m_pageOff; // The offset in the mmaped region to the data. + off_t m_mapOff; // The offset in the file to the mmaped region. + mutable const char *m_data; // Pointer to the mmaped region. +}; + class JSBigOptimizedBundleString : public JSBigString { public: enum class Encoding { @@ -157,7 +212,6 @@ public: Utf16, }; - JSBigOptimizedBundleString(int fd, size_t size, const uint8_t sha1[20], Encoding encoding) : m_fd(fd), m_size(size), diff --git a/ReactCommon/cxxreact/tests/BUCK b/ReactCommon/cxxreact/tests/BUCK index fb51122aa..8ea3af385 100644 --- a/ReactCommon/cxxreact/tests/BUCK +++ b/ReactCommon/cxxreact/tests/BUCK @@ -1,6 +1,7 @@ TEST_SRCS = [ 'CxxMessageQueueTest.cpp', 'jsarg_helpers.cpp', + 'jsbigstring.cpp', 'jscexecutor.cpp', 'jsclogging.cpp', 'methodcall.cpp', @@ -35,6 +36,7 @@ if THIS_IS_FBOBJC: '-fexceptions', ], deps = [ + '//xplat/folly:molly', '//xplat/third-party/gmock:gtest', react_native_xplat_target('cxxreact:bridge'), react_native_xplat_target('jschelpers:jschelpers'), diff --git a/ReactCommon/cxxreact/tests/jsbigstring.cpp b/ReactCommon/cxxreact/tests/jsbigstring.cpp new file mode 100644 index 000000000..31e9adc5f --- /dev/null +++ b/ReactCommon/cxxreact/tests/jsbigstring.cpp @@ -0,0 +1,61 @@ + +// Copyright 2004-present Facebook. All Rights Reserved. +#include +#include + +#include +#include +#include +#include +#include + +using namespace facebook; +using namespace facebook::react; + +namespace { +int tempFileFromString(std::string contents) +{ + std::string tmp {getenv("TMPDIR")}; + tmp += "/temp.XXXXX"; + + std::vector tmpBuf {tmp.begin(), tmp.end()}; + tmpBuf.push_back('\0'); + + const int fd = mkstemp(tmpBuf.data()); + write(fd, contents.c_str(), contents.size() + 1); + + return fd; +} +}; + +TEST(JSBigFileString, MapWholeFileTest) { + std::string data {"Hello, world"}; + const auto size = data.length() + 1; + + // Initialise Big String + int fd = tempFileFromString("Hello, world"); + JSBigFileString bigStr {fd, size}; + + // Test + ASSERT_EQ(fd, bigStr.fd()); + ASSERT_STREQ(data.c_str(), bigStr.c_str()); +} + +TEST(JSBigFileString, MapPartTest) { + std::string data {"Hello, world"}; + + // Sub-string to actually map + std::string needle {"or"}; + off_t offset = data.find(needle); + + // Initialise Big String + int fd = tempFileFromString(data); + JSBigFileString bigStr {fd, needle.size(), offset}; + + // Test + ASSERT_EQ(fd, bigStr.fd()); + ASSERT_EQ(needle.length(), bigStr.size()); + for (unsigned int i = 0; i < needle.length(); ++i) { + ASSERT_EQ(needle[i], bigStr.c_str()[i]); + } +}