mysqlshdk/scripting/polyglot/native_wrappers/polyglot_native_wrapper.h (206 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
*/
#ifndef MYSQLSHDK_SCRIPTING_POLYGLOT_NATIVE_WRAPPERS_POLYGLOT_NATIVE_WRAPPER_
#define MYSQLSHDK_SCRIPTING_POLYGLOT_NATIVE_WRAPPERS_POLYGLOT_NATIVE_WRAPPER_
#include <cassert>
#include <memory>
#include <type_traits>
#include "mysqlshdk/scripting/polyglot/utils/polyglot_api_clean.h"
#include "mysqlshdk/include/shellcore/interrupt_handler.h"
#include "mysqlshdk/scripting/polyglot/languages/polyglot_language.h"
#include "mysqlshdk/scripting/polyglot/native_wrappers/polyglot_collectable.h"
#include "mysqlshdk/scripting/polyglot/utils/graalvm_exceptions.h"
#include "mysqlshdk/scripting/polyglot/utils/polyglot_error.h"
namespace shcore {
namespace polyglot {
class Polyglot_language;
/**
* Exposure of C++ objects to the polyglot library is done through the usage of
* Proxy objects provided by the polyglot library. Those proxy objects implement
* the needed operations to treat a C++ object as a standard polyglot object,
* each of those operations require a C++ callback for the execution.
*
* In the C++ side it is needed to create the callbacks required by the proxy
* objects in the polyglot library, for each C++ object type to be supported
* there's a wrapper class which provides the needed callbacks.
*
* When a C++ object is given to the polyglot library, 2 things are needed:
*
* - The registrations of the C++ callbacks
* - The identification of the target C++ instance to be used on the callbacks
*
* Each object wrapper provides the above. For the second item, a Collectable
* object is created and given to the polyglot proxy object. This Collectable
* object contains the necessary information to identify the target C++ object
* on which the operation will be executed.
*
* This class holds general callback function to be used to release the created
* Collectable objects once the Proxy object is garbage collected in the
* Polyglot library.
*/
template <typename T, Collectable_type t>
class Polyglot_native_wrapper {
public:
using Collectable_t = Collectable<T, t>;
using Native_ptr = std::shared_ptr<T>;
Polyglot_native_wrapper() = delete;
explicit Polyglot_native_wrapper(std::weak_ptr<Polyglot_language> language)
: m_language(std::move(language)) {}
Polyglot_native_wrapper(const Polyglot_native_wrapper &) = delete;
Polyglot_native_wrapper &operator=(const Polyglot_native_wrapper &) = delete;
Polyglot_native_wrapper(Polyglot_native_wrapper &&) = delete;
Polyglot_native_wrapper &operator=(Polyglot_native_wrapper &&) = delete;
virtual ~Polyglot_native_wrapper() = default;
poly_value wrap(const Native_ptr &native_value) const {
auto collectable =
std::make_unique<Collectable_t>(native_value, m_language);
auto language = collectable->language();
auto value = create_wrapper(language->thread(), language->context(),
collectable.get());
collectable->registry()->add(collectable.release());
return value;
}
static bool unwrap(poly_thread thread, poly_value value,
Native_ptr *ret_object) {
assert(ret_object);
void *data = nullptr;
if (!is_native_type(thread, value, t, &data)) {
return false;
}
assert(data);
const auto collectable = static_cast<Collectable_t *>(data);
if (const auto &target_native = collectable->data()) {
*ret_object = target_native;
}
return true;
}
protected:
static poly_value handler_release_collectable(poly_thread thread,
poly_callback_info args) {
void *data = nullptr;
if (get_data(thread, args, "destroy", &data)) {
assert(data);
const auto collectable = static_cast<ICollectable *>(data);
auto registry = collectable->registry();
if (registry) {
registry->remove(collectable);
}
}
return nullptr;
}
virtual poly_value create_wrapper(poly_thread thread, poly_context context,
ICollectable *collectable) const = 0;
/**
* Generic handler to be used with pure native functions, no interaction with
* the polyglot library is done:
*
* - Receive no arguments
* - Return a Value as result
*/
template <typename Config>
static poly_value native_handler_no_args(poly_thread thread,
poly_callback_info args) {
void *data = nullptr;
poly_value value = nullptr;
if (get_data(thread, args, Config::name, &data)) {
assert(data);
const auto collectable = static_cast<Collectable_t *>(data);
const auto language = collectable->language();
try {
value = language->convert(Config::callback(collectable->data()));
} catch (const Exception &exc) {
language->throw_exception_object(exc.error());
} catch (const Polyglot_error &exc) {
language->throw_exception_object(exc);
} catch (const Graalvm_exception &exc) {
language->throw_graalvm_exception(exc);
} catch (const std::exception &e) {
throw_callback_exception(thread, e.what());
}
}
return value;
}
/**
* Generic handler to be used with functions that interact with the polyglot
* library:
*
* - Receive no arguments
* - Return a poly_value as result
*/
template <typename Config>
static poly_value polyglot_handler_no_args(poly_thread thread,
poly_callback_info args) {
void *data = nullptr;
poly_value value = nullptr;
if (get_data(thread, args, Config::name, &data)) {
assert(data);
const auto collectable = static_cast<Collectable_t *>(data);
const auto language = collectable->language();
try {
value = Config::callback(language, collectable->data());
} catch (const Exception &exc) {
language->throw_exception_object(exc.error());
} catch (const Polyglot_error &exc) {
language->throw_exception_object(exc);
} catch (const Graalvm_exception &exc) {
language->throw_graalvm_exception(exc);
} catch (const std::exception &e) {
throw_callback_exception(thread, e.what());
}
}
return value;
}
/**
* Generic handler to be used with pure native functions, no interaction with
* the polyglot library is done:
*
* - Receive a vector of Values as arguments
* - Return a Value as result
*/
template <typename Config>
static poly_value native_handler_fixed_args(poly_thread thread,
poly_callback_info args) {
std::vector<poly_value> argv;
void *data = nullptr;
poly_value value = nullptr;
try {
if (get_args_and_data(thread, args, Config::name, &data, Config::argc,
&argv)) {
assert(data);
const auto collectable = static_cast<Collectable_t *>(data);
const auto language = collectable->language();
try {
value = language->convert(Config::callback(
collectable->data(), language->convert_args(argv)));
} catch (const Exception &exc) {
language->throw_exception_object(exc.error());
} catch (const Polyglot_error &exc) {
language->throw_exception_object(exc);
} catch (const Graalvm_exception &exc) {
language->throw_graalvm_exception(exc);
}
}
} catch (const std::exception &e) {
throw_callback_exception(thread, e.what());
}
return value;
}
/**
* Generic handler to be used with functions that interact with the polyglot
* library, this is:
*
* - Require the language instance for the execution
* - Receive a vector of polyglot values as arguments
* - Return a polyglot value as result
*/
template <typename Config>
static poly_value polyglot_handler_fixed_args(poly_thread thread,
poly_callback_info args) {
std::vector<poly_value> argv;
void *data = nullptr;
poly_value value = nullptr;
try {
if (get_args_and_data(thread, args, Config::name, &data, Config::argc,
&argv)) {
assert(data);
const auto collectable = static_cast<Collectable_t *>(data);
const auto language = collectable->language();
try {
value = Config::callback(language, collectable->data(), argv);
} catch (const Exception &exc) {
language->throw_exception_object(exc.error());
} catch (const Polyglot_error &exc) {
language->throw_exception_object(exc);
} catch (const Graalvm_exception &exc) {
language->throw_graalvm_exception(exc);
}
}
} catch (const std::exception &e) {
throw_callback_exception(thread, e.what());
}
return value;
}
/**
* Generic handler to be used with pure native functions, no interaction with
* the polyglot library is done:
*
* - Receive a vector of Values as arguments
* - Return a Value as result
*/
template <typename Config>
static poly_value native_handler_variable_args(poly_thread thread,
poly_callback_info args) {
std::vector<poly_value> argv;
void *data = nullptr;
poly_value value = nullptr;
std::shared_ptr<Polyglot_language> language = {};
try {
parse_callback_args(thread, args, &argv, &data);
assert(data);
const auto collectable = static_cast<Collectable_t *>(data);
// Since the polyglot library will not raise an interrupted exception when
// calling native code, we have to handle that here
bool interrupted = false;
shcore::Interrupt_handler handler([&]() {
interrupted = true;
return true;
});
language = collectable->language();
const auto v =
Config::callback(collectable->data(), language->convert_args(argv));
if (interrupted) {
throw std::runtime_error(k_interrupted_error);
}
value = language->convert(v);
} catch (const Exception &exc) {
language->throw_exception_object(exc.error());
} catch (const Polyglot_error &exc) {
language->throw_exception_object(exc);
} catch (const Graalvm_exception &exc) {
language->throw_graalvm_exception(exc);
} catch (const std::exception &e) {
throw_callback_exception(thread, e.what());
}
return value;
}
std::weak_ptr<Polyglot_language> m_language;
};
} // namespace polyglot
} // namespace shcore
#endif // MYSQLSHDK_SCRIPTING_POLYGLOT_NATIVE_WRAPPERS_POLYGLOT_NATIVE_WRAPPER_