<commit_before>////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

#include <cassert>
#include <dlfcn.h>
#include <map>
#include <string>

#include "rpc.hpp"
#include "jsc_init.hpp"

#include "base64.hpp"
#include "object_accessor.hpp"
#include "shared_realm.hpp"
#include "results.hpp"

using namespace realm;
using namespace realm::rpc;

using Accessor = NativeAccessor<JSValueRef, JSContextRef>;

static const char * const RealmObjectTypesData = "data";
static const char * const RealmObjectTypesDate = "date";
static const char * const RealmObjectTypesDictionary = "dict";
static const char * const RealmObjectTypesFunction = "function";
static const char * const RealmObjectTypesList = "list";
static const char * const RealmObjectTypesObject = "object";
static const char * const RealmObjectTypesResults = "results";
static const char * const RealmObjectTypesRealm = "realm";
static const char * const RealmObjectTypesUndefined = "undefined";

static RPCServer*& get_rpc_server(JSGlobalContextRef ctx) {
    static std::map<JSGlobalContextRef, RPCServer*> s_map;
    return s_map[ctx];
}

RPCWorker::RPCWorker() {
    m_thread = std::thread([this]() {
        // TODO: Create ALooper/CFRunLoop to support async calls.
        while (!m_stop) {
            try_run_task();
        }
    });
}

RPCWorker::~RPCWorker() {
    m_stop = true;
    m_thread.join();
}

void RPCWorker::add_task(std::function<json()> task) {
    m_tasks.push_back(std::packaged_task<json()>(task));
}

json RPCWorker::pop_task_result() {
    // This might block until a future has been added.
    auto future = m_futures.pop_back();

    // This will block until a return value (or exception) is available.
    return future.get();
}

void RPCWorker::try_run_task() {
    try {
        // Use a 10 millisecond timeout to keep this thread unblocked.
        auto task = m_tasks.pop_back(10);
        task();

        // Since this can be called recursively, it must be pushed to the front of the queue *after* running the task.
        m_futures.push_front(task.get_future());
    }
    catch (ConcurrentDequeTimeout &) {
        // We tried.
    }
}

RPCServer::RPCServer() {
    m_context = JSGlobalContextCreate(NULL);
    get_rpc_server(m_context) = this;

    // JavaScriptCore crashes when trying to walk up the native stack to print the stacktrace.
    // FIXME: Avoid having to do this!
    static void (*setIncludesNativeCallStack)(JSGlobalContextRef, bool) = (void (*)(JSGlobalContextRef, bool))dlsym(RTLD_DEFAULT, "JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions");
    if (setIncludesNativeCallStack) {
        setIncludesNativeCallStack(m_context, false);
    }

    m_requests["/create_session"] = [this](const json dict) {
        RJSInitializeInContext(m_context);

        jsc::String realm_string = "Realm";
        JSObjectRef realm_constructor = jsc::Object::validated_get_constructor(m_context, JSContextGetGlobalObject(m_context), realm_string);

        m_session_id = store_object(realm_constructor);
        return (json){{"result", m_session_id}};
    };
    m_requests["/create_realm"] = [this](const json dict) {
        JSObjectRef realm_constructor = m_session_id ? JSObjectRef(m_objects[m_session_id]) : NULL;
        if (!realm_constructor) {
            throw std::runtime_error("Realm constructor not found!");
        }

        json::array_t args = dict["arguments"];
        size_t arg_count = args.size();
        JSValueRef arg_values[arg_count];

        for (size_t i = 0; i < arg_count; i++) {
            arg_values[i] = deserialize_json_value(args[i]);
        }

        JSObjectRef realm_object = jsc::Function::construct(m_context, realm_constructor, arg_count, arg_values);
        RPCObjectID realm_id = store_object(realm_object);
        return (json){{"result", realm_id}};
    };
    m_requests["/call_method"] = [this](const json dict) {
        JSObjectRef object = m_objects[dict["id"].get<RPCObjectID>()];
        std::string method_string = dict["name"].get<std::string>();
        JSObjectRef function = jsc::Object::validated_get_function(m_context, object, method_string);

        json args = dict["arguments"];
        size_t arg_count = args.size();
        JSValueRef arg_values[arg_count];
        for (size_t i = 0; i < arg_count; i++) {
            arg_values[i] = deserialize_json_value(args[i]);
        }

        JSValueRef result = jsc::Function::call(m_context, function, object, arg_count, arg_values);
        return (json){{"result", serialize_json_value(result)}};
    };
    m_requests["/get_property"] = [this](const json dict) {
        RPCObjectID oid = dict["id"].get<RPCObjectID>();
        json name = dict["name"];
        JSValueRef value;

        if (name.is_number()) {
            value = jsc::Object::get_property(m_context, m_objects[oid], name.get<unsigned int>());
        }
        else {
            value = jsc::Object::get_property(m_context, m_objects[oid], name.get<std::string>());
        }

        return (json){{"result", serialize_json_value(value)}};
    };
    m_requests["/set_property"] = [this](const json dict) {
        RPCObjectID oid = dict["id"].get<RPCObjectID>();
        json name = dict["name"];
        JSValueRef value = deserialize_json_value(dict["value"]);

        if (name.is_number()) {
            jsc::Object::set_property(m_context, m_objects[oid], name.get<unsigned int>(), value);
        }
        else {
            jsc::Object::set_property(m_context, m_objects[oid], name.get<std::string>(), value);
        }

        return json::object();
    };
    m_requests["/dispose_object"] = [this](const json dict) {
        RPCObjectID oid = dict["id"].get<RPCObjectID>();
        m_objects.erase(oid);
        return json::object();
    };
    m_requests["/clear_test_state"] = [this](const json dict) {
        for (auto object : m_objects) {
            // The session ID points to the Realm constructor object, which should remain.
            if (object.first != m_session_id) {
                m_objects.erase(object.first);
            }
        }
        m_callbacks.clear();
        JSGarbageCollect(m_context);
        js::delete_all_realms();
        return json::object();
    };
}

RPCServer::~RPCServer() {
    get_rpc_server(m_context) = nullptr;
    JSGlobalContextRelease(m_context);
}

void RPCServer::run_callback(JSContextRef ctx, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], jsc::ReturnValue &return_value) {
    RPCServer* server = get_rpc_server(JSContextGetGlobalContext(ctx));
    if (!server) {
        return;
    }

    // The first argument was curried to be the callback id.
    RPCObjectID callback_id = jsc::Value::to_number(ctx, arguments[0]);
    JSObjectRef arguments_array = jsc::Object::create_array(ctx, uint32_t(argc - 1), argc == 1 ? nullptr : arguments + 1);
    json arguments_json = server->serialize_json_value(arguments_array);

    // The next task on the stack will instruct the JS to run this callback.
    // This captures references since it will be executed before exiting this function.
    server->m_worker.add_task([&]() -> json {
        return {
            {"callback", callback_id},
            {"arguments", arguments_json},
        };
    });<commit_msg>Clear protected value maps in RPCServer destructor<commit_after>////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////

#include <cassert>
#include <dlfcn.h>
#include <map>
#include <string>

#include "rpc.hpp"
#include "jsc_init.hpp"

#include "base64.hpp"
#include "object_accessor.hpp"
#include "shared_realm.hpp"
#include "results.hpp"

using namespace realm;
using namespace realm::rpc;

using Accessor = NativeAccessor<JSValueRef, JSContextRef>;

static const char * const RealmObjectTypesData = "data";
static const char * const RealmObjectTypesDate = "date";
static const char * const RealmObjectTypesDictionary = "dict";
static const char * const RealmObjectTypesFunction = "function";
static const char * const RealmObjectTypesList = "list";
static const char * const RealmObjectTypesObject = "object";
static const char * const RealmObjectTypesResults = "results";
static const char * const RealmObjectTypesRealm = "realm";
static const char * const RealmObjectTypesUndefined = "undefined";

static RPCServer*& get_rpc_server(JSGlobalContextRef ctx) {
    static std::map<JSGlobalContextRef, RPCServer*> s_map;
    return s_map[ctx];
}

RPCWorker::RPCWorker() {
    m_thread = std::thread([this]() {
        // TODO: Create ALooper/CFRunLoop to support async calls.
        while (!m_stop) {
            try_run_task();
        }
    });
}

RPCWorker::~RPCWorker() {
    m_stop = true;
    m_thread.join();
}

void RPCWorker::add_task(std::function<json()> task) {
    m_tasks.push_back(std::packaged_task<json()>(task));
}

json RPCWorker::pop_task_result() {
    // This might block until a future has been added.
    auto future = m_futures.pop_back();

    // This will block until a return value (or exception) is available.
    return future.get();
}

void RPCWorker::try_run_task() {
    try {
        // Use a 10 millisecond timeout to keep this thread unblocked.
        auto task = m_tasks.pop_back(10);
        task();

        // Since this can be called recursively, it must be pushed to the front of the queue *after* running the task.
        m_futures.push_front(task.get_future());
    }
    catch (ConcurrentDequeTimeout &) {
        // We tried.
    }
}

RPCServer::RPCServer() {
    m_context = JSGlobalContextCreate(NULL);
    get_rpc_server(m_context) = this;

    // JavaScriptCore crashes when trying to walk up the native stack to print the stacktrace.
    // FIXME: Avoid having to do this!
    static void (*setIncludesNativeCallStack)(JSGlobalContextRef, bool) = (void (*)(JSGlobalContextRef, bool))dlsym(RTLD_DEFAULT, "JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions");
    if (setIncludesNativeCallStack) {
        setIncludesNativeCallStack(m_context, false);
    }

    m_requests["/create_session"] = [this](const json dict) {
        RJSInitializeInContext(m_context);

        jsc::String realm_string = "Realm";
        JSObjectRef realm_constructor = jsc::Object::validated_get_constructor(m_context, JSContextGetGlobalObject(m_context), realm_string);

        m_session_id = store_object(realm_constructor);
        return (json){{"result", m_session_id}};
    };
    m_requests["/create_realm"] = [this](const json dict) {
        JSObjectRef realm_constructor = m_session_id ? JSObjectRef(m_objects[m_session_id]) : NULL;
        if (!realm_constructor) {
            throw std::runtime_error("Realm constructor not found!");
        }

        json::array_t args = dict["arguments"];
        size_t arg_count = args.size();
        JSValueRef arg_values[arg_count];

        for (size_t i = 0; i < arg_count; i++) {
            arg_values[i] = deserialize_json_value(args[i]);
        }

        JSObjectRef realm_object = jsc::Function::construct(m_context, realm_constructor, arg_count, arg_values);
        RPCObjectID realm_id = store_object(realm_object);
        return (json){{"result", realm_id}};
    };
    m_requests["/call_method"] = [this](const json dict) {
        JSObjectRef object = m_objects[dict["id"].get<RPCObjectID>()];
        std::string method_string = dict["name"].get<std::string>();
        JSObjectRef function = jsc::Object::validated_get_function(m_context, object, method_string);

        json args = dict["arguments"];
        size_t arg_count = args.size();
        JSValueRef arg_values[arg_count];
        for (size_t i = 0; i < arg_count; i++) {
            arg_values[i] = deserialize_json_value(args[i]);
        }

        JSValueRef result = jsc::Function::call(m_context, function, object, arg_count, arg_values);
        return (json){{"result", serialize_json_value(result)}};
    };
    m_requests["/get_property"] = [this](const json dict) {
        RPCObjectID oid = dict["id"].get<RPCObjectID>();
        json name = dict["name"];
        JSValueRef value;

        if (name.is_number()) {
            value = jsc::Object::get_property(m_context, m_objects[oid], name.get<unsigned int>());
        }
        else {
            value = jsc::Object::get_property(m_context, m_objects[oid], name.get<std::string>());
        }

        return (json){{"result", serialize_json_value(value)}};
    };
    m_requests["/set_property"] = [this](const json dict) {
        RPCObjectID oid = dict["id"].get<RPCObjectID>();
        json name = dict["name"];
        JSValueRef value = deserialize_json_value(dict["value"]);

        if (name.is_number()) {
            jsc::Object::set_property(m_context, m_objects[oid], name.get<unsigned int>(), value);
        }
        else {
            jsc::Object::set_property(m_context, m_objects[oid], name.get<std::string>(), value);
        }

        return json::object();
    };
    m_requests["/dispose_object"] = [this](const json dict) {
        RPCObjectID oid = dict["id"].get<RPCObjectID>();
        m_objects.erase(oid);
        return json::object();
    };
    m_requests["/clear_test_state"] = [this](const json dict) {
        for (auto object : m_objects) {
            // The session ID points to the Realm constructor object, which should remain.
            if (object.first != m_session_id) {
                m_objects.erase(object.first);
            }
        }
        m_callbacks.clear();
        JSGarbageCollect(m_context);
        js::delete_all_realms();
        return json::object();
    };
}

RPCServer::~RPCServer() {
    // The protected values should be unprotected before releasing the context.
    m_objects.clear();
    m_callbacks.clear();

    get_rpc_server(m_context) = nullptr;
    JSGlobalContextRelease(m_context);
}

void RPCServer::run_callback(JSContextRef ctx, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], jsc::ReturnValue &return_value) {
    RPCServer* server = get_rpc_server(JSContextGetGlobalContext(ctx));
    if (!server) {
        return;
    }

    // The first argument was curried to be the callback id.
    RPCObjectID callback_id = jsc::Value::to_number(ctx, arguments[0]);
    JSObjectRef arguments_array = jsc::Object::create_array(ctx, uint32_t(argc - 1), argc == 1 ? nullptr : arguments + 1);
    json arguments_json = server->serialize_json_value(arguments_array);

    // The next task on the stack will instruct the JS to run this callback.
    // This captures references since it will be executed before exiting this function.
    server->m_worker.add_task([&]() -> json {
        return {
            {"callback", callback_id},
            {"arguments", arguments_json},
        };
    });
{'fraction_non_alphanumeric': 0.14007709524993783, 'fraction_numerical': 0.002735637901019647, 'mean_word_length': 3.105410923940786, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5846384', 'n_tokens_mistral': 4781, 'n_tokens_neox': 4701, 'n_words': 1186}