#include <JavaScriptCore/JavaScript.h>
#include <cstddef>
#include <cstdint>
#include <utility>
#if __has_include(<expected>)
#include <expected>
#endif
#if !defined(__cpp_lib_expected)
namespace std {
namespace experimental {
inline namespace fundamentals_v3 {
template<class E> class unexpected {
public:
constexpr explicit unexpected(const E& error)
: m_error(error)
{
}
constexpr explicit unexpected(E&& error)
: m_error(std::move(error))
{
}
constexpr E& error() &
{
return m_error;
}
constexpr const E& error() const&
{
return m_error;
}
constexpr E&& error() &&
{
return std::move(m_error);
}
constexpr const E&& error() const&&
{
return std::move(m_error);
}
private:
E m_error;
};
}
}
template<class E> using unexpected = experimental::unexpected<E>;
}
#endif
#if __has_include("InitializeThreading.h")
#include "InitializeThreading.h"
#define RONG_JSC_CAN_INITIALIZE 1
#elif __has_include(<JavaScriptCore/InitializeThreading.h>)
#include <JavaScriptCore/InitializeThreading.h>
#define RONG_JSC_CAN_INITIALIZE 1
#endif
extern "C" {
typedef struct {
uint8_t* data; size_t size; const char* error; } RongJSCBytecodeResult;
typedef struct {
JSValueRef value; int is_exception; const char* error; } RongJSCRunBytecodeResult;
}
extern "C" void rong_jsc_initialize(void)
{
#if defined(RONG_JSC_CAN_INITIALIZE)
JSC::initialize();
#endif
}
#if defined(RONG_JSC_HAVE_PRIVATE_HEADERS)
#if __has_include("config.h")
#include "config.h"
#endif
#ifndef JS_EXPORT_PRIVATE
#define JS_EXPORT_PRIVATE
#endif
#ifndef WTF_EXPORT_PRIVATE
#define WTF_EXPORT_PRIVATE
#endif
#if __has_include(<JavaScriptCore/APICast.h>)
#include <JavaScriptCore/APICast.h>
#include <JavaScriptCore/BytecodeCacheError.h>
#include <JavaScriptCore/CachedBytecode.h>
#include <JavaScriptCore/Completion.h>
#include <JavaScriptCore/Exception.h>
#include <JavaScriptCore/SourceCode.h>
#include <JavaScriptCore/SourceProvider.h>
#include <JavaScriptCore/SourceTaintedOrigin.h>
#include <JavaScriptCore/ThrowScope.h>
#include <JavaScriptCore/VM.h>
#include <JavaScriptCore/JSCInlines.h>
#else
#include "APICast.h"
#include "BytecodeCacheError.h"
#include "CachedBytecode.h"
#include "Completion.h"
#include "Exception.h"
#include "SourceCode.h"
#include "SourceProvider.h"
#include "SourceTaintedOrigin.h"
#include "ThrowScope.h"
#include "VM.h"
#include "JSCInlines.h"
#endif
#include <cstring>
#include <limits>
#include <span>
#include <wtf/FileHandle.h>
#include <wtf/MallocSpan.h>
#include <wtf/StdLibExtras.h>
#include <wtf/text/CString.h>
namespace {
constexpr uint8_t kMagic[] = { 'R', 'J', 'S', 'C', 'B', 'C', '1', 0 };
constexpr uint32_t kEnvelopeVersion = 1;
constexpr size_t kMagicSize = sizeof(kMagic);
constexpr size_t kVersionOffset = kMagicSize;
constexpr size_t kSourceLenOffset = kVersionOffset + sizeof(uint32_t);
constexpr size_t kURLLenOffset = kSourceLenOffset + sizeof(uint64_t);
constexpr size_t kHeaderSize = kURLLenOffset + sizeof(uint64_t);
static_assert(kMagicSize == 8);
template<typename StringType = String>
String stringFromUTF8(const char* data, size_t len)
requires requires { StringType::fromUTF8(std::span<const char>(data, len)); }
{
return StringType::fromUTF8(std::span<const char>(data, len));
}
template<typename StringType = String>
String stringFromUTF8(const char* data, size_t len)
requires (!requires { StringType::fromUTF8(std::span<const char>(data, len)); })
{
return StringType::fromUTF8(data, len);
}
char* copyCString(const char* message)
{
if (!message)
message = "unknown JavaScriptCore bytecode bridge error";
size_t len = std::strlen(message);
char* out = static_cast<char*>(fastMalloc(len + 1));
std::memcpy(out, message, len + 1);
return out;
}
char* copyString(const String& message)
{
auto utf8 = message.utf8();
return copyCString(utf8.data());
}
RongJSCBytecodeResult compileError(const char* message)
{
return { nullptr, 0, copyCString(message) };
}
RongJSCBytecodeResult compileError(const String& message)
{
return { nullptr, 0, copyString(message) };
}
RongJSCRunBytecodeResult runError(const char* message)
{
return { nullptr, 0, copyCString(message) };
}
RongJSCRunBytecodeResult runException(JSValueRef value)
{
return { value, 1, nullptr };
}
RongJSCRunBytecodeResult runValue(JSValueRef value)
{
return { value, 0, nullptr };
}
void writeU32LE(uint8_t* dest, uint32_t value)
{
dest[0] = static_cast<uint8_t>(value);
dest[1] = static_cast<uint8_t>(value >> 8);
dest[2] = static_cast<uint8_t>(value >> 16);
dest[3] = static_cast<uint8_t>(value >> 24);
}
void writeU64LE(uint8_t* dest, uint64_t value)
{
for (unsigned i = 0; i < 8; ++i)
dest[i] = static_cast<uint8_t>(value >> (i * 8));
}
uint32_t readU32LE(const uint8_t* bytes)
{
return static_cast<uint32_t>(bytes[0])
| (static_cast<uint32_t>(bytes[1]) << 8)
| (static_cast<uint32_t>(bytes[2]) << 16)
| (static_cast<uint32_t>(bytes[3]) << 24);
}
uint64_t readU64LE(const uint8_t* bytes)
{
uint64_t value = 0;
for (unsigned i = 0; i < 8; ++i)
value |= static_cast<uint64_t>(bytes[i]) << (i * 8);
return value;
}
JSValueRef makeErrorValue(JSContextRef ctx, const char* message)
{
JSStringRef messageString = JSStringCreateWithUTF8CString(message);
JSValueRef args[] = { JSValueMakeString(ctx, messageString) };
JSValueRef exception = nullptr;
JSObjectRef error = JSObjectMakeError(ctx, 1, args, &exception);
JSStringRelease(messageString);
if (exception)
return exception;
return error;
}
JSC::SourceCode makeProgramSource(const String& source, const String& sourceURL)
{
JSC::SourceOrigin origin { URL({ }, sourceURL) };
return JSC::makeSource(
source,
origin,
JSC::SourceTaintedOrigin::Untainted,
sourceURL,
TextPosition(),
JSC::SourceProviderSourceType::Program);
}
class RongCachedSourceProvider final : public JSC::SourceProvider {
public:
static Ref<RongCachedSourceProvider> create(
String source,
const JSC::SourceOrigin& sourceOrigin,
String sourceURL,
RefPtr<JSC::CachedBytecode>&& bytecode)
{
return adoptRef(*new RongCachedSourceProvider(
WTF::move(source),
sourceOrigin,
WTF::move(sourceURL),
WTF::move(bytecode)));
}
unsigned hash() const override
{
return m_source.hash();
}
StringView source() const override
{
return m_source;
}
RefPtr<JSC::CachedBytecode> cachedBytecode() const override
{
return m_bytecode;
}
private:
RongCachedSourceProvider(
String&& source,
const JSC::SourceOrigin& sourceOrigin,
String&& sourceURL,
RefPtr<JSC::CachedBytecode>&& bytecode)
: JSC::SourceProvider(
sourceOrigin,
WTF::move(sourceURL),
String(),
JSC::SourceTaintedOrigin::Untainted,
TextPosition(),
JSC::SourceProviderSourceType::Program)
, m_source(WTF::move(source))
, m_bytecode(WTF::move(bytecode))
{
}
String m_source;
RefPtr<JSC::CachedBytecode> m_bytecode;
};
}
extern "C" {
int rong_jsc_bytecode_supported(void) {
return 1;
}
void rong_jsc_free_bytecode(uint8_t* data) {
if (data) {
fastFree(data);
}
}
void rong_jsc_free_error(const char* error) {
if (error) {
fastFree(const_cast<char*>(error));
}
}
RongJSCBytecodeResult rong_jsc_compile_to_bytecode(
JSContextRef ctx,
const char* source,
size_t source_len,
const char* source_url)
{
using namespace JSC;
if (!ctx)
return compileError("invalid JavaScriptCore context");
if (!source && source_len)
return compileError("invalid JavaScript source pointer");
if (!source_url)
return compileError("invalid JavaScript source URL");
JSGlobalObject* globalObject = toJS(ctx);
VM& vm = globalObject->vm();
JSLockHolder lock(vm);
auto scope = DECLARE_THROW_SCOPE(vm);
String sourceString = stringFromUTF8(source, source_len);
String urlString = String::fromUTF8(source_url);
SourceCode sourceCode = makeProgramSource(sourceString, urlString);
BytecodeCacheError error;
FileSystem::FileHandle bytecodeFile;
RefPtr<CachedBytecode> bytecode = generateProgramBytecode(
vm,
sourceCode,
bytecodeFile,
error);
if (auto* exception = scope.exception()) {
auto message = exception->value().toWTFString(globalObject);
(void)scope.tryClearException();
return compileError(message);
}
if (!bytecode) {
if (error.isValid())
return compileError(error.message());
return compileError("JavaScriptCore failed to compile source to bytecode");
}
auto payload = bytecode->span();
const uint8_t* payloadData = payload.data();
size_t payloadSize = payload.size();
if (!payloadData || payloadSize == 0) {
return compileError("JavaScriptCore bytecode compilation produced an empty payload");
}
size_t urlLen = std::strlen(source_url);
uint64_t sourceSize = static_cast<uint64_t>(source_len);
uint64_t urlSize = static_cast<uint64_t>(urlLen);
if (source_len > std::numeric_limits<size_t>::max() - kHeaderSize
|| urlLen > std::numeric_limits<size_t>::max() - kHeaderSize - source_len
|| payloadSize > std::numeric_limits<size_t>::max() - kHeaderSize - source_len - urlLen)
return compileError("JavaScriptCore bytecode envelope is too large");
size_t totalSize = kHeaderSize + source_len + urlLen + payloadSize;
uint8_t* buffer = static_cast<uint8_t*>(fastMalloc(totalSize));
if (!buffer) {
return compileError("allocation failed");
}
std::memcpy(buffer, kMagic, kMagicSize);
writeU32LE(buffer + kVersionOffset, kEnvelopeVersion);
writeU64LE(buffer + kSourceLenOffset, sourceSize);
writeU64LE(buffer + kURLLenOffset, urlSize);
std::memcpy(buffer + kHeaderSize, source, source_len);
std::memcpy(buffer + kHeaderSize + source_len, source_url, urlLen);
std::memcpy(buffer + kHeaderSize + source_len + urlLen, payloadData, payloadSize);
return { buffer, totalSize, nullptr };
}
RongJSCRunBytecodeResult rong_jsc_run_bytecode(
JSContextRef ctx,
const uint8_t* bytes,
size_t size)
{
using namespace JSC;
if (!ctx)
return runError("invalid JavaScriptCore context");
if (!bytes && size)
return runError("invalid JavaScriptCore bytecode pointer");
JSGlobalObject* globalObject = toJS(ctx);
VM& vm = globalObject->vm();
JSLockHolder lock(vm);
auto scope = DECLARE_THROW_SCOPE(vm);
if (size < kHeaderSize || std::memcmp(bytes, kMagic, kMagicSize) != 0)
return runException(makeErrorValue(ctx, "Invalid JavaScriptCore bytecode envelope"));
uint32_t storedVersion = readU32LE(bytes + kVersionOffset);
if (storedVersion != kEnvelopeVersion) {
return runException(makeErrorValue(ctx, "Unsupported JavaScriptCore bytecode envelope version"));
}
uint64_t sourceSize64 = readU64LE(bytes + kSourceLenOffset);
if (sourceSize64 > static_cast<uint64_t>(size - kHeaderSize))
return runException(makeErrorValue(ctx, "Invalid JavaScriptCore bytecode source length"));
size_t sourceSize = static_cast<size_t>(sourceSize64);
uint64_t urlSize64 = readU64LE(bytes + kURLLenOffset);
if (urlSize64 > static_cast<uint64_t>(size - kHeaderSize - sourceSize))
return runException(makeErrorValue(ctx, "Invalid JavaScriptCore bytecode URL length"));
size_t urlSize = static_cast<size_t>(urlSize64);
size_t payloadSize = size - kHeaderSize - sourceSize - urlSize;
if (!payloadSize)
return runException(makeErrorValue(ctx, "Invalid empty JavaScriptCore bytecode payload"));
const uint8_t* sourceStart = bytes + kHeaderSize;
const uint8_t* urlStart = sourceStart + sourceSize;
const uint8_t* payloadStart = urlStart + urlSize;
auto payloadCopy = MallocSpan<uint8_t, VMMalloc>::malloc(payloadSize);
if (!payloadCopy) {
return runException(makeErrorValue(ctx, "JavaScriptCore bytecode allocation failed"));
}
std::memcpy(payloadCopy.mutableSpan().data(), payloadStart, payloadSize);
RefPtr<CachedBytecode> cachedBytecode =
CachedBytecode::create(WTF::move(payloadCopy), { });
String urlString = stringFromUTF8(reinterpret_cast<const char*>(urlStart), urlSize);
SourceOrigin origin { URL({ }, urlString) };
String sourceString = stringFromUTF8(reinterpret_cast<const char*>(sourceStart), sourceSize);
SourceCode sourceCode(RongCachedSourceProvider::create(
sourceString,
origin,
urlString,
WTF::move(cachedBytecode)));
NakedPtr<Exception> returnedException;
JSValue result = JSC::evaluate(globalObject, sourceCode, JSValue(), returnedException);
if (returnedException)
return runException(toRef(globalObject, returnedException->value()));
if (auto* exception = scope.exception()) {
JSValueRef exceptionValue = toRef(globalObject, exception->value());
(void)scope.tryClearException();
return runException(exceptionValue);
}
return runValue(toRef(globalObject, result));
}
}
#else
extern "C" {
int rong_jsc_bytecode_supported(void) {
return 0;
}
void rong_jsc_free_bytecode(uint8_t* ) {
}
void rong_jsc_free_error(const char* ) {
}
RongJSCBytecodeResult rong_jsc_compile_to_bytecode(
JSContextRef ,
const char* ,
size_t ,
const char* )
{
return { nullptr, 0,
"bytecode is unsupported: JSC artifact built without private headers" };
}
RongJSCRunBytecodeResult rong_jsc_run_bytecode(
JSContextRef ,
const uint8_t* ,
size_t )
{
return { nullptr, 0, nullptr };
}
}
#endif