react-native-windows/vnext/Shared/BaseScriptStoreImpl.cpp

346 строки
10 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "pch.h"
#include "BaseScriptStoreImpl.h"
#include "Hasher.h"
#include "MemoryMappedBuffer.h"
#include <CppRuntimeOptions.h>
// C++/WinRT
#include <winrt/base.h>
// Standard Library
#include <fstream>
namespace facebook {
namespace react {
namespace {
class ByteArrayBuffer : public facebook::jsi::Buffer {
public:
size_t size() const override {
return size_;
}
const uint8_t *data() const override {
return byteArray_.get();
}
uint8_t *data() {
return byteArray_.get();
}
ByteArrayBuffer(size_t size) : size_(size), byteArray_(std::make_unique<uint8_t[]>(size)) {}
ByteArrayBuffer(ByteArrayBuffer &&) = default;
ByteArrayBuffer &operator=(ByteArrayBuffer &&) = default;
private:
ByteArrayBuffer(const ByteArrayBuffer &) = delete;
ByteArrayBuffer &operator=(const ByteArrayBuffer &) = delete;
size_t size_;
std::unique_ptr<uint8_t[]> byteArray_;
};
class BufferViewBuffer : public facebook::jsi::Buffer {
public:
size_t size() const override {
return size_;
}
const uint8_t *data() const override {
return buffer_->data() + offset_;
}
uint8_t *data() {
return const_cast<uint8_t *>(buffer_->data()) + offset_;
}
BufferViewBuffer(std::unique_ptr<const facebook::jsi::Buffer> buffer, size_t offset, size_t size)
: buffer_(std::move(buffer)), offset_(offset), size_(size) {
if (size_ > buffer_->size() - offset)
std::terminate();
}
BufferViewBuffer(BufferViewBuffer &&) = default;
BufferViewBuffer &operator=(BufferViewBuffer &&) = default;
private:
BufferViewBuffer(const BufferViewBuffer &) = delete;
BufferViewBuffer &operator=(const BufferViewBuffer &) = delete;
std::unique_ptr<const facebook::jsi::Buffer> buffer_;
size_t offset_;
size_t size_;
};
constexpr const char *PERSIST_MAGIC = "RNWPREP";
constexpr const char *PERSIST_EOF = "EOF";
int constexpr length__(const char *str) {
return *str ? 1 + length__(str + 1) : 0;
}
// TODO : Enforce any packing policy ?
struct PreparedScriptPrefix {
// TODO :: constexpr initialize the array.
char magic[length__(PERSIST_MAGIC)];
jsi::ScriptVersion_t scriptVersion;
jsi::JSRuntimeVersion_t runtimeVersion;
uint64_t sizeInBytes;
std::uint8_t hash[32];
};
struct PreparedScriptSuffix {
char eof[length__(PERSIST_EOF)];
};
} // namespace
jsi::VersionedBuffer BaseScriptStoreImpl::getVersionedScript(const std::string &url) noexcept {
std::ifstream file(url, std::ios::binary | std::ios::ate);
if (!file) {
return {nullptr, 0};
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
auto buffer = std::make_unique<ByteArrayBuffer>(static_cast<size_t>(size));
if (!file.read(reinterpret_cast<char *>(buffer->data()), size)) {
return {nullptr, 0};
}
file.close();
return {std::move(buffer), versionProvider_ ? versionProvider_->getVersion(url) : static_cast<uint64_t>(size)};
}
jsi::ScriptVersion_t BaseScriptStoreImpl::getScriptVersion(const std::string &url) noexcept {
if (versionProvider_) {
return versionProvider_->getVersion(url);
} else {
std::ifstream file(url, std::ios::binary | std::ios::ate);
if (!file) {
return 0;
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
return static_cast<uint64_t>(size);
}
}
std::unique_ptr<const jsi::Buffer> LocalFileSimpleBufferStore::getBuffer(const std::string &bufferId) noexcept {
// 1. Store path must be set
// 2. It must be a directory that exists. TODO :: Figure out a cross platform
// way to ensure this.
// 3. Directory must end with the path delimiter. TODO :: Verify this.
if (storeDirectory_.empty()) {
std::terminate();
}
if (Microsoft::React::GetRuntimeOptionBool("JSI.MemoryMappedScriptStore")) {
try {
return Microsoft::JSI::MakeMemoryMappedBuffer(winrt::to_hstring(storeDirectory_ + bufferId).c_str());
} catch (const facebook::jsi::JSINativeException &) {
return nullptr;
}
} else {
// Treat buffer id as the relative path fragment.
std::ifstream file(storeDirectory_ + bufferId, std::ios::binary | std::ios::ate);
if (!file) {
return nullptr;
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
auto buffer = std::make_unique<ByteArrayBuffer>(static_cast<size_t>(size));
if (!file.read(reinterpret_cast<char *>(buffer->data()), size)) {
return nullptr;
}
return buffer;
}
}
bool LocalFileSimpleBufferStore::persistBuffer(
const std::string &relativeUrl,
std::unique_ptr<const jsi::Buffer> buffer) noexcept {
// Assumptions on storeDirectory_ same as in getRawBuffer
if (storeDirectory_.empty())
std::terminate();
std::ofstream file;
file.open(storeDirectory_ + relativeUrl, std::ios::binary | std::ios::trunc);
if (!file)
return false;
file.write(reinterpret_cast<const char *>(buffer->data()), buffer->size());
file.close();
return true;
}
std::string BasePreparedScriptStoreImpl::getPreparedScriptFileName(
const jsi::ScriptSignature &scriptSignature,
const jsi::JSRuntimeSignature &runtimeSignature,
const char *prepareTag) {
// Essentially, we are trying to construct,
// prep_<source_url>_<runtime_id>_<preparation_tag>.cache
std::string preparedScriptFileName("prep_");
const std::string &scriptUrl = scriptSignature.url;
// As a crude heuristic we choose the last 64 characters of the source url.
constexpr int MAXLENGTH = 64;
preparedScriptFileName.append(
scriptUrl.begin() + ((scriptUrl.size() < MAXLENGTH) ? 0 : (scriptUrl.size() - MAXLENGTH)), scriptUrl.end());
// Make a valid file name.
std::replace(preparedScriptFileName.begin(), preparedScriptFileName.end(), '\\', '_');
std::replace(preparedScriptFileName.begin(), preparedScriptFileName.end(), '/', '_');
std::replace(preparedScriptFileName.begin(), preparedScriptFileName.end(), ':', '_');
std::replace(preparedScriptFileName.begin(), preparedScriptFileName.end(), '.', '_');
if (runtimeSignature.runtimeName.empty()) {
std::terminate();
}
preparedScriptFileName.append("_");
preparedScriptFileName.append(runtimeSignature.runtimeName);
if (prepareTag) {
preparedScriptFileName.append("_");
preparedScriptFileName.append(prepareTag);
}
// TODO :: Need to construct a hash. ref:
// https://en.wikipedia.org/wiki/Base64#Filenames
// extension
preparedScriptFileName.append(".cache");
return preparedScriptFileName;
}
std::shared_ptr<const jsi::Buffer> BasePreparedScriptStoreImpl::tryGetPreparedScript(
const jsi::ScriptSignature &scriptSignature,
const jsi::JSRuntimeSignature &runtimeSignature,
const char *prepareTag) noexcept {
std::string preparedScriptFilePath = getPreparedScriptFileName(scriptSignature, runtimeSignature, prepareTag);
auto buffer = bufferStore_->getBuffer(preparedScriptFilePath);
if (!buffer) {
return nullptr;
}
const PreparedScriptPrefix *prefix = reinterpret_cast<const PreparedScriptPrefix *>(buffer->data());
if (strncmp(prefix->magic, PERSIST_MAGIC, sizeof(prefix->magic)) != 0) {
// magic value doesn't match!! The store is very likely corrupted or belongs
// to old version.
return nullptr;
}
if (prefix->scriptVersion != scriptSignature.version) {
// script version don't match!! Need to regenerate cache.
return nullptr;
}
if (prefix->runtimeVersion != runtimeSignature.version) {
// Runtime changed after the cache generation.
return nullptr;
}
if (prefix->sizeInBytes != buffer->size() - sizeof(PreparedScriptPrefix) - sizeof(PreparedScriptSuffix)) {
// Size is not as expected. Store is possibly corrupted .. It is safer to
// bail out.
return nullptr;
}
std::optional<std::vector<std::uint8_t>> hashBuffer = Microsoft::ReactNative::GetSHA256Hash(
reinterpret_cast<const std::uint8_t *>(buffer->data()) + sizeof(PreparedScriptPrefix),
static_cast<size_t>(prefix->sizeInBytes));
if (!hashBuffer) {
// Hashing failed.
return nullptr;
}
if (hashBuffer.value().size() < sizeof(prefix->hash)) {
// Unexpected hash size.
return nullptr;
}
if (memcmp(hashBuffer.value().data(), prefix->hash, sizeof(prefix->hash)) != 0) {
// Hash doesn't match. Store is possibly corrupted. It is safer to bail out.
return nullptr;
}
const PreparedScriptSuffix *suffix = reinterpret_cast<const PreparedScriptSuffix *>(
buffer->data() + sizeof(PreparedScriptPrefix) + prefix->sizeInBytes);
if (strncmp(suffix->eof, PERSIST_EOF, sizeof(suffix->eof)) != 0) {
// magic value doesn't match!! The store is very likely corrupted or belongs
// to old version.
return nullptr;
}
return std::make_shared<BufferViewBuffer>(
std::move(buffer), sizeof(PreparedScriptPrefix), static_cast<size_t>(prefix->sizeInBytes));
}
void BasePreparedScriptStoreImpl::persistPreparedScript(
std::shared_ptr<const jsi::Buffer> preparedScript,
const jsi::ScriptSignature &scriptMetadata,
const jsi::JSRuntimeSignature &runtimeMetadata,
const char *prepareTag) noexcept {
// TODO :: Unfortunately, The current abstraction is forcing us to make a
// copy. Need to re-evaluate.
auto newBuffer = std::make_unique<ByteArrayBuffer>(
sizeof(PreparedScriptPrefix) + preparedScript->size() + sizeof(PreparedScriptSuffix));
PreparedScriptPrefix *prefix = reinterpret_cast<PreparedScriptPrefix *>(newBuffer->data());
memcpy_s(prefix->magic, sizeof(prefix->magic), PERSIST_MAGIC, sizeof(prefix->magic));
prefix->scriptVersion = scriptMetadata.version;
prefix->runtimeVersion = runtimeMetadata.version;
prefix->sizeInBytes = preparedScript->size();
std::optional<std::vector<std::uint8_t>> hashBuffer =
Microsoft::ReactNative::GetSHA256Hash(preparedScript->data(), preparedScript->size());
if (!hashBuffer) {
// Hashing failed.
std::terminate();
}
memcpy_s(prefix->hash, sizeof(prefix->hash), hashBuffer.value().data(), hashBuffer.value().size());
memcpy_s(
newBuffer->data() + sizeof(PreparedScriptPrefix),
newBuffer->size() - sizeof(PreparedScriptPrefix),
preparedScript->data(),
preparedScript->size());
PreparedScriptSuffix *suffix = reinterpret_cast<PreparedScriptSuffix *>(
newBuffer->data() + sizeof(PreparedScriptPrefix) + preparedScript->size());
memcpy_s(suffix->eof, sizeof(suffix->eof), PERSIST_EOF, sizeof(suffix->eof));
std::string preparedScriptFilePath = getPreparedScriptFileName(scriptMetadata, runtimeMetadata, prepareTag);
bufferStore_->persistBuffer(preparedScriptFilePath, std::move(newBuffer));
}
} // namespace react
} // namespace facebook