mysqlshdk/scripting/polyglot/polyglot_context.cc (225 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/polyglot_context.h"
#include <cerrno>
#include <list>
#include <stack>
#include "my_config.h"
#include "mysqlshdk/include/scripting/module_registry.h"
#include "mysqlshdk/include/scripting/obj_date.h"
#include "mysqlshdk/include/scripting/object_factory.h"
#include "mysqlshdk/include/scripting/object_registry.h"
#include "mysqlshdk/include/shellcore/base_shell.h" // FIXME
#include "mysqlshdk/include/shellcore/console.h"
#include "mysqlshdk/include/shellcore/scoped_contexts.h"
#include "mysqlshdk/libs/utils/logger.h"
#include "mysqlshdk/libs/utils/utils_file.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/languages/polyglot_language.h"
#include "mysqlshdk/scripting/polyglot/native_wrappers/polyglot_collectable.h"
#include "mysqlshdk/scripting/polyglot/native_wrappers/polyglot_object_wrapper.h"
#include "mysqlshdk/scripting/polyglot/polyglot_type_conversion.h"
#include "mysqlshdk/scripting/polyglot/polyglot_wrappers/types_polyglot.h"
#include "mysqlshdk/scripting/polyglot/shell_javascript.h"
#include "mysqlshdk/scripting/polyglot/utils/polyglot_error.h"
#include "mysqlshdk/scripting/polyglot/utils/polyglot_utils.h"
#include "scripting/jscript_core_definitions.h"
#ifdef HAVE_JS
#include "mysqlshdk/scripting/polyglot/languages/polyglot_javascript.h"
#endif
namespace shcore {
namespace polyglot {
Polyglot_context::Polyglot_context(Object_registry *registry, Language type) {
m_common_context.initialize({});
m_language = get_language(type);
assert(m_language);
m_language->initialize();
set_global("globals", Value(registry->_registry));
}
std::shared_ptr<Polyglot_language> Polyglot_context::get_language(
Language type) {
#ifdef HAVE_JS
if (type == Language::JAVASCRIPT) {
return std::make_shared<Shell_javascript>(&m_common_context);
}
#endif
return {};
}
poly_context Polyglot_context::context() const { return m_language->context(); }
poly_thread Polyglot_context::thread() const { return m_language->thread(); }
Polyglot_context::~Polyglot_context() {
set_global("globals", Value{});
m_language->finalize();
m_common_context.finalize();
}
void Polyglot_context::set_global(const std::string &name, const Value &value) {
m_language->set_global(name, convert(value));
}
Value Polyglot_context::get_global(const std::string &name) {
return convert(m_language->get_global(name).get());
}
std::tuple<polyglot::Store, std::string> Polyglot_context::get_global_polyglot(
const std::string &name) {
auto global{m_language->get_global(name)};
if (global.get() == nullptr) {
return {};
}
if (m_language->is_object(global.get())) {
auto obj_type = m_language->to_string(type_info(global.get()));
if (shcore::str_beginswith(obj_type, "m.")) {
obj_type = obj_type.substr(2);
}
return std::make_tuple(std::move(global), std::move(obj_type));
} else if (is_native_type(thread(), global.get(), Collectable_type::OBJECT)) {
auto obj_type = m_language->to_string(type_info(global.get()));
if (shcore::str_beginswith(obj_type, "m.")) {
obj_type = obj_type.substr(2);
}
return std::make_tuple(std::move(global), std::move(obj_type));
} else {
return {};
}
}
std::vector<std::pair<bool, std::string>> Polyglot_context::list_globals() {
auto globals = m_language->globals();
auto names = globals->get_members();
std::vector<std::pair<bool, std::string>> ret;
for (auto &name : names) {
auto member = get_member(m_language->thread(), globals->get(), name);
ret.emplace_back(is_executable(m_language->thread(), member),
std::move(name));
}
// In graal, the print function for being built in is not returned as member
// of the globals object, so it is injected here
ret.emplace_back(true, "print");
return ret;
}
const std::vector<std::string> &Polyglot_context::keywords() const {
return m_language->keywords();
}
std::tuple<bool, polyglot::Store, std::string> Polyglot_context::get_member_of(
const poly_value obj, const std::string &name) {
auto member = get_member(thread(), obj, name);
if (member) {
auto type = to_string(thread(), type_info(member));
if (shcore::str_beginswith(type, "m.")) {
type = type.substr(2);
}
return {is_executable(thread(), member), Store(thread(), member),
std::move(type)};
}
return {false, Store(nullptr), ""};
}
std::vector<std::pair<bool, std::string>> Polyglot_context::get_members_of(
const poly_value obj) {
auto props = get_member_keys(thread(), context(), obj);
std::vector<std::pair<bool, std::string>> keys;
for (auto &prop : props) {
auto member = get_member(thread(), obj, prop);
keys.emplace_back(is_executable(thread(), member), std::move(prop));
}
return keys;
}
Value Polyglot_context::convert(poly_value value) const {
return m_language->convert(value);
}
poly_value Polyglot_context::convert(const Value &value) const {
return m_language->convert(value);
}
poly_value Polyglot_context::type_info(poly_value value) const {
return m_language->type_info(value);
}
void Polyglot_context::set_argv(const std::vector<std::string> &argv) {
shcore::Value args_value = get_global("sys").as_object()->get_member("argv");
auto args = args_value.as_array();
args->clear();
for (auto &arg : argv) {
args->push_back(Value(arg));
}
}
std::pair<Value, bool> Polyglot_context::execute(const std::string &code_str,
const std::string &source) {
shcore::Scoped_naming_style style(m_language->naming_style());
try {
return m_language->execute(code_str, source);
} catch (Polyglot_error &error) {
if (m_language->is_terminating() &&
(error.is_interrupted() ||
shcore::str_beginswith(error.message(), "Interrupted"))) {
mysqlsh::current_console()->print_diag(
"Script execution interrupted by user.");
} else {
throw Exception::scripting_error(error.format(false));
}
}
return {Value(), true};
}
std::pair<Value, bool> Polyglot_context::execute_interactive(
const std::string &code_str, Input_state *r_state, bool flush) noexcept {
shcore::Scoped_naming_style style(m_language->naming_style());
m_language->clear_is_terminating();
*r_state = Input_state::Ok;
// automatically release unreferenced local variables
Polyglot_scope scope{thread()};
poly_value result;
auto rc = m_language->eval(k_origin_shell, code_str.c_str(), &result);
if (rc == poly_ok) {
return {convert(result), false};
}
auto exception = Polyglot_error(thread(), rc);
const auto console = mysqlsh::current_console();
if (m_language->is_terminating() &&
(exception.is_interrupted() ||
shcore::str_beginswith(exception.message(), "Interrupted"))) {
console->print_diag("Script execution interrupted by user.");
} else {
// If there's no more input then the interpreter should not
// continue in continued mode so any error should bubble up
if (!flush) {
// check if this was an error of type
// - SyntaxError: "Expected } but found eof"
// - SyntaxError: "Missing close quote"
// which we treat as a multiline mode trigger or
// - SyntaxError: Expected an operand but found error
// which may be a sign of unfinished C style comment
constexpr std::string_view k_unexpected_eof = "but found eof";
constexpr std::string_view k_expected_binding =
"expected BindingIdentifier or BindingPattern";
constexpr std::string_view k_missing_closing_quote =
"Missing close quote";
constexpr std::string_view k_expected_operand =
"Expected an operand but found error";
if (exception.is_syntax_error()) {
auto message = exception.message();
if (message.find(k_unexpected_eof) != std::string::npos ||
message.find(k_expected_binding) != std::string::npos ||
// For backticks the column is reported as 0 all the time,
// wihle for the double and single quote it is always the
// quote location + 1
(message == k_missing_closing_quote &&
exception.column().has_value() && *exception.column() == 0)) {
*r_state = Input_state::ContinuedBlock;
} else if (message == k_expected_operand) {
auto comment_pos = code_str.rfind("/*");
while (comment_pos != std::string::npos &&
code_str.find("*/", comment_pos + 2) == std::string::npos) {
rc = poly_context_parse(
thread(), context(), m_language->get_language_id(),
k_origin_shell, code_str.substr(0, comment_pos).c_str(),
&result);
if (poly_ok == rc) {
*r_state = Input_state::ContinuedSingle;
} else {
exception = Polyglot_error(thread(), rc);
message = exception.message();
if (message.find(k_unexpected_eof) != std::string::npos ||
message.find(k_expected_binding) != std::string::npos) {
*r_state = Input_state::ContinuedBlock;
} else if (message == k_expected_operand) {
comment_pos = code_str.rfind("/*", comment_pos - 1);
continue;
}
}
break;
}
}
}
}
if (*r_state == Input_state::Ok) {
console->print_diag(exception.format(true));
}
}
return {Value(), true};
}
void Polyglot_context::terminate() { m_language->terminate(); }
bool Polyglot_context::load_plugin(const Plugin_definition &plugin) {
return m_language->load_plugin(plugin.file);
}
} // namespace polyglot
} // namespace shcore