mysqlshdk/scripting/polyglot/languages/polyglot_language.cc (339 lines of code) (raw):
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "mysqlshdk/scripting/polyglot/languages/polyglot_language.h"
#include <algorithm>
#include <cassert>
#include <exception>
#include <functional>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
#include "mysqlshdk/scripting/polyglot/utils/polyglot_api_clean.h"
#include "mysqlshdk/include/scripting/types.h"
#include "mysqlshdk/include/shellcore/scoped_contexts.h"
#include "mysqlshdk/libs/utils/logger.h"
#include "mysqlshdk/libs/utils/utils_general.h"
#include "mysqlshdk/libs/utils/utils_path.h"
#include "mysqlshdk/libs/utils/utils_string.h"
#include "mysqlshdk/scripting/polyglot/native_wrappers/polyglot_collectable.h"
#include "mysqlshdk/scripting/polyglot/native_wrappers/polyglot_file_system_wrapper.h"
#include "mysqlshdk/scripting/polyglot/utils/graalvm_exceptions.h"
#include "mysqlshdk/scripting/polyglot/utils/polyglot_error.h"
#include "scripting/jscript_core_definitions.h"
namespace shcore {
namespace polyglot {
Polyglot_language::Current_script::Current_script() : m_root(path::getcwd()) {
// fake top-level script
push("mysqlsh");
}
std::string Polyglot_language::Current_script::current_folder() const {
return path::dirname(current_script());
}
void Polyglot_language::Current_script::push(const std::string &script) {
m_scripts.push(path::is_absolute(script)
? script
: path::normalize(path::join_path(m_root, script)));
}
Scoped_global::Scoped_global(const Polyglot_language *language,
poly_value value)
: m_language(language) {
assert(m_language);
m_name = shcore::str_format(
"___%s___",
shcore::get_random_string(10, "abcdefABCDEF0123456789").c_str());
if (value != nullptr) {
m_language->globals()->set_poly_member(m_name, value);
}
}
poly_value Scoped_global::execute(const std::string &code) {
poly_value result;
auto actual_code = shcore::str_replace(code, "<<global>>", m_name);
if (const auto rc = m_language->eval("(internal)", actual_code, &result);
rc != poly_ok) {
throw Polyglot_error(m_language->thread(), rc);
}
return result;
}
Scoped_global::~Scoped_global() {
try {
m_language->globals()->remove_member(m_name);
} catch (const std::exception &error) {
log_error("polyglot error while cleaning temporary global '%s': %s",
m_name.c_str(), error.what());
}
}
Polyglot_language::Polyglot_language(Polyglot_common_context *common_context,
const std::string &debug_port)
: m_common_context{common_context}, m_debug_port{debug_port} {}
void Polyglot_language::enable_debug() {
throw_if_error(poly_context_builder_option, thread(), m_context_builder,
"inspect", m_debug_port.c_str());
throw_if_error(poly_context_builder_option, thread(), m_context_builder,
"inspect.Suspend", "false");
throw_if_error(poly_context_builder_option, thread(), m_context_builder,
"inspect.WaitAttached", "false");
}
void Polyglot_language::init_context_builder() {
m_context_builder = NULL;
throw_if_error(poly_create_context_builder, thread(), nullptr, 0,
&m_context_builder);
auto engine = !m_debug_port.empty() ? nullptr : m_common_context->engine();
if (engine) {
throw_if_error(poly_context_builder_engine, m_thread, m_context_builder,
engine);
}
throw_if_error(poly_context_builder_allow_all_access, thread(),
m_context_builder, false);
throw_if_error(poly_context_builder_allow_buffer_access_constrained_policy,
thread(), m_context_builder, true);
throw_if_error(poly_context_builder_output, thread(), m_context_builder,
&Polyglot_language::output_callback,
&Polyglot_language::error_callback, this);
}
void Polyglot_language::initialize(const std::shared_ptr<IFile_system> &fs) {
m_file_system = fs;
if (const auto rc =
poly_attach_thread(m_common_context->isolate(), &m_thread);
rc != poly_ok) {
throw Polyglot_generic_error(
shcore::str_format("error attaching thread to isolate: %d", rc));
}
m_scope = std::make_unique<Polyglot_scope>(thread());
m_storage = std::make_unique<Polyglot_storage>(thread());
init_context_builder();
{
// If there's no file system and debug should be enabled, we should do it
// here, otherwise we need to wait until the file system is set
if (!m_debug_port.empty() && !m_file_system) {
enable_debug();
}
poly_context context;
throw_if_error(poly_context_builder_build, thread(), m_context_builder,
&context);
m_context = Store(thread(), context);
}
m_types = std::make_unique<Polyglot_type_bridger>(shared_from_this());
m_types->init();
// NOTE: The file system is a weird citizen, because it is a ProxyObject,
// which means it requires to be created using a specific context, however,
// for a context to use a custom file system, the file system needs to be
// specified on the context builder (yes, before the context is created).
// This problem is handled as follows:
// The context builder is initialized and a context is created (lines above
// this comment). If a custom file system is specified then the
// set_file_system function will update the context builder to specify the
// proxy file system and a new context will be created, to finally update
// the context in the file system to the one that will be used in the
// language.
if (m_file_system) {
set_file_system();
}
{
Polyglot_scope context_creation_scope(thread());
poly_value globals;
if (const auto rc = poly_context_get_bindings(thread(), context(),
get_language_id(), &globals);
rc != poly_ok) {
throw Polyglot_generic_error("error getting context bindings");
}
m_globals = std::make_shared<Polyglot_object>(m_types.get(), thread(),
context(), globals, "");
}
}
void Polyglot_language::finalize() {
m_globals.reset();
m_types->dispose();
shcore::Scoped_callback cleanup{[this]() {
m_context.reset();
m_storage->clear();
m_scope->close();
}};
if (const auto rc = poly_context_close(thread(), context(), true);
rc != poly_ok) {
throw Polyglot_error(thread(), rc);
}
m_common_context->clean_collectables();
}
void Polyglot_language::throw_graalvm_exception(
const Graalvm_exception &exception) {
throw_if_error(poly_throw_custom_exception, thread(), exception.id(),
exception.what());
}
shcore::Value Polyglot_language::to_native_object(
poly_value object, const std::string &class_name) {
return Value(std::make_shared<polyglot::Polyglot_object>(
m_types.get(), thread(), context(), object, class_name));
}
int64_t Polyglot_language::eval(const std::string &source,
const std::string &code_str,
poly_value *result) const {
auto ret_val =
poly_context_eval(thread(), context(), get_language_id(),
source.empty() ? k_origin_shell : source.c_str(),
code_str.c_str(), result);
return ret_val;
}
poly_value Polyglot_language::create_source(const std::string &path) const {
poly_value source;
shcore::polyglot::throw_if_error(poly_create_source, thread(),
get_language_id(), path.c_str(), &source);
return source;
}
std::pair<Value, bool> Polyglot_language::debug(const std::string &path) {
poly_value source = create_source(path);
poly_value result;
if (const auto rc =
poly_context_eval_source(thread(), context(), source, &result);
rc != poly_ok) {
throw Polyglot_error(thread(), rc);
}
return {convert(result), false};
}
std::pair<Value, bool> Polyglot_language::execute(const std::string &code_str,
const std::string &source) {
const auto &origin = source.empty() ? k_origin_shell : source;
const auto script_scope = enter_script(origin);
clear_is_terminating();
poly_value result;
if (const auto rc = eval(origin, code_str, &result); rc != poly_ok) {
throw Polyglot_error(thread(), rc);
}
return {convert(result), false};
}
void Polyglot_language::output_callback(const char *bytes, size_t length,
void *data) {
auto self = static_cast<Polyglot_language *>(data);
self->output_handler(bytes, length);
}
void Polyglot_language::error_callback(const char *bytes, size_t length,
void *data) {
auto self = static_cast<Polyglot_language *>(data);
self->error_handler(bytes, length);
}
std::string Polyglot_language::current_script_folder() const {
return m_current_script.current_folder();
}
void Polyglot_language::throw_exception_object(poly_value exception) const {
if (const auto rc = poly_throw_exception_object(thread(), exception);
rc != poly_ok) {
log_error("While throwing exception, another exception occurred: %s",
Polyglot_error(thread(), rc).what());
}
}
void Polyglot_language::throw_exception_object(
const shcore::Dictionary_t &data) const {
try {
auto exception = create_exception_object(data->get_string(k_key_message),
convert(shcore::Value(data)));
throw_exception_object(exception);
} catch (const std::exception &error) {
log_error("While throwing exception, another exception occurred: %s",
error.what());
}
}
void Polyglot_language::throw_exception_object(
const Polyglot_error &error) const {
throw_exception_object(error.data());
}
void Polyglot_language::set_global(const std::string &name,
const Value &value) const {
set_global(name, convert(value));
}
void Polyglot_language::set_global(const std::string &name,
poly_value value) const {
m_globals->set_poly_member(name, value);
}
void Polyglot_language::set_global_function(const std::string &name,
poly_callback callback,
void *data) {
auto function = wrap_callback(callback, data ? data : this);
set_global(name, function);
}
Store Polyglot_language::get_global(const std::string &name) const {
return Store(thread(), m_globals->get_poly_member(name));
}
poly_value Polyglot_language::poly_string(const std::string &data) const {
return polyglot::poly_string(thread(), context(), data);
}
std::string Polyglot_language::to_string(poly_value obj) const {
return polyglot::to_string(thread(), obj);
}
void Polyglot_language::terminate() {
m_terminating = true;
poly_thread i_thread;
if (poly_ok != poly_attach_thread(m_common_context->isolate(), &i_thread)) {
throw Polyglot_generic_error("Error attaching thread on terminate handler");
}
throw_if_error(poly_context_interrupt, i_thread, context());
if (const auto rc = poly_detach_thread(i_thread); rc != poly_ok) {
throw Polyglot_generic_error(shcore::str_format(
"Error detaching thread on terminate handler: %d", rc));
}
}
bool Polyglot_language::is_terminating() const { return m_terminating; }
void Polyglot_language::clear_is_terminating() { m_terminating = false; }
poly_value Polyglot_language::wrap_callback(poly_callback callback,
void *data) const {
poly_value function;
throw_if_error(poly_create_function, thread(), context(), callback, data,
&function);
return function;
}
/**
* Context needs to be either deleted immediately (i.e. in case of an error),
* or stored till JS context exists (in order to preserve execution
* environment).
*/
poly_context Polyglot_language::copy_global_context() const {
poly_context new_context;
throw_if_error(poly_context_builder_build, thread(), m_context_builder,
&new_context);
poly_value new_globals;
if (const auto rc = poly_context_get_bindings(
thread(), new_context, get_language_id(), &new_globals);
rc != poly_ok) {
throw Polyglot_generic_error("error getting context bindings");
}
auto members = m_globals->get_members();
for (const auto &member : members) {
poly_value poly_member;
throw_if_error(poly_value_get_member, thread(), m_globals->get(),
member.c_str(), &poly_member);
throw_if_error(poly_value_put_member, thread(), new_globals, member.c_str(),
poly_member);
}
return new_context;
}
poly_context Polyglot_language::context() const { return m_context.get(); }
poly_thread Polyglot_language::thread() const { return m_thread; }
const std::shared_ptr<Polyglot_object> &Polyglot_language::globals() const {
return m_globals;
}
Value Polyglot_language::convert(poly_value value) const {
return m_types->poly_value_to_native_value(value);
}
poly_value Polyglot_language::convert(const Value &value) const {
return m_types->native_value_to_poly_value(value);
}
Argument_list Polyglot_language::convert_args(
const std::vector<poly_value> &args) const {
return m_types->convert_args(args);
}
poly_value Polyglot_language::type_info(poly_value value) const {
return m_types->type_info(value);
}
poly_reference Polyglot_language::store(poly_value value) {
return m_storage->store(value);
}
void Polyglot_language::erase(poly_reference value) { m_storage->erase(value); }
bool Polyglot_language::get_member(poly_value object, const char *name,
poly_value *member) const {
bool has_member{false};
throw_if_error(poly_value_has_member, thread(), object, name, &has_member);
if (has_member) {
throw_if_error(poly_value_get_member, thread(), object, name, member);
}
return has_member;
}
Polyglot_language::Script_scope Polyglot_language::enter_script(
const std::string &s) {
return Script_scope(this, s);
}
void Polyglot_language::set_file_system() {
Polyglot_file_system_wrapper fs_wrapper(shared_from_this());
auto poly_fs = fs_wrapper.wrap(m_file_system);
throw_if_error(poly_context_builder_set_file_system, thread(),
m_context_builder, poly_fs);
if (!m_debug_port.empty()) {
enable_debug();
}
poly_context context;
throw_if_error(poly_context_builder_build, thread(), m_context_builder,
&context);
m_context = Store(thread(), context);
throw_if_error(poly_file_system_set_context, thread(), poly_fs,
m_context.get());
}
} // namespace polyglot
} // namespace shcore