rb_ext_resolve_symbol: C API to resolve and return externed symbols [Feature #20005]
This is a C API for extensions to resolve and get function symbols of other extensions. Extensions can check the expected symbol is correctly loaded and accessible, and use it if it is available. Otherwise, extensions can raise their own error to guide users to setup their environments correctly and what's missing.
This commit is contained in:
parent
8a37df8c8b
commit
e51f9e9f75
@ -8542,6 +8542,7 @@ load.$(OBJEXT): $(top_srcdir)/internal/dir.h
|
||||
load.$(OBJEXT): $(top_srcdir)/internal/error.h
|
||||
load.$(OBJEXT): $(top_srcdir)/internal/file.h
|
||||
load.$(OBJEXT): $(top_srcdir)/internal/gc.h
|
||||
load.$(OBJEXT): $(top_srcdir)/internal/hash.h
|
||||
load.$(OBJEXT): $(top_srcdir)/internal/imemo.h
|
||||
load.$(OBJEXT): $(top_srcdir)/internal/load.h
|
||||
load.$(OBJEXT): $(top_srcdir)/internal/parse.h
|
||||
|
4
dln.c
4
dln.c
@ -463,8 +463,8 @@ dln_symbol(void *handle, const char *symbol)
|
||||
}
|
||||
if (handle == NULL) {
|
||||
# if defined(USE_DLN_DLOPEN)
|
||||
handle = dlopen(NULL, 0);
|
||||
# elif defined(_WIN32) && defined(RUBY_EXPORT)
|
||||
handle = dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL);
|
||||
# elif defined(_WIN32)
|
||||
handle = rb_libruby_handle();
|
||||
# else
|
||||
return NULL;
|
||||
|
1
ext/-test-/load/resolve_symbol_resolver/extconf.rb
Normal file
1
ext/-test-/load/resolve_symbol_resolver/extconf.rb
Normal file
@ -0,0 +1 @@
|
||||
create_makefile('-test-/load/resolve_symbol_resolver')
|
@ -0,0 +1,50 @@
|
||||
#include <ruby.h>
|
||||
#include "ruby/internal/intern/load.h"
|
||||
|
||||
typedef VALUE(*target_func)(VALUE);
|
||||
|
||||
static target_func rst_any_method;
|
||||
|
||||
VALUE
|
||||
rsr_any_method(VALUE klass)
|
||||
{
|
||||
return rst_any_method((VALUE)NULL);
|
||||
}
|
||||
|
||||
VALUE
|
||||
rsr_try_resolve_fname(VALUE klass)
|
||||
{
|
||||
target_func rst_something_missing =
|
||||
(target_func) rb_ext_resolve_symbol("-test-/load/resolve_symbol_missing", "rst_any_method");
|
||||
if (rst_something_missing == NULL) {
|
||||
// This should be done in Init_*, so the error is LoadError
|
||||
rb_raise(rb_eLoadError, "symbol not found: missing fname");
|
||||
}
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
VALUE
|
||||
rsr_try_resolve_sname(VALUE klass)
|
||||
{
|
||||
target_func rst_something_missing =
|
||||
(target_func)rb_ext_resolve_symbol("-test-/load/resolve_symbol_target", "rst_something_missing");
|
||||
if (rst_something_missing == NULL) {
|
||||
// This should be done in Init_*, so the error is LoadError
|
||||
rb_raise(rb_eLoadError, "symbol not found: missing sname");
|
||||
}
|
||||
return Qtrue;
|
||||
}
|
||||
|
||||
void
|
||||
Init_resolve_symbol_resolver(void)
|
||||
{
|
||||
VALUE mod = rb_define_module("ResolveSymbolResolver");
|
||||
rb_define_singleton_method(mod, "any_method", rsr_any_method, 0);
|
||||
rb_define_singleton_method(mod, "try_resolve_fname", rsr_try_resolve_fname, 0);
|
||||
rb_define_singleton_method(mod, "try_resolve_sname", rsr_try_resolve_sname, 0);
|
||||
|
||||
rst_any_method = (target_func)rb_ext_resolve_symbol("-test-/load/resolve_symbol_target", "rst_any_method");
|
||||
if (rst_any_method == NULL) {
|
||||
rb_raise(rb_eLoadError, "resolve_symbol_target is not loaded");
|
||||
}
|
||||
}
|
1
ext/-test-/load/resolve_symbol_target/extconf.rb
Normal file
1
ext/-test-/load/resolve_symbol_target/extconf.rb
Normal file
@ -0,0 +1 @@
|
||||
create_makefile('-test-/load/resolve_symbol_target')
|
@ -0,0 +1,15 @@
|
||||
#include <ruby.h>
|
||||
#include "resolve_symbol_target.h"
|
||||
|
||||
VALUE
|
||||
rst_any_method(VALUE klass)
|
||||
{
|
||||
return rb_str_new_cstr("from target");
|
||||
}
|
||||
|
||||
void
|
||||
Init_resolve_symbol_target(void)
|
||||
{
|
||||
VALUE mod = rb_define_module("ResolveSymbolTarget");
|
||||
rb_define_singleton_method(mod, "any_method", rst_any_method, 0);
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
LIBRARY resolve_symbol_target
|
||||
EXPORTS
|
||||
Init_resolve_symbol_target
|
||||
rst_any_method
|
@ -0,0 +1,4 @@
|
||||
#include <ruby.h>
|
||||
#include "ruby/internal/dllexport.h"
|
||||
|
||||
RUBY_EXTERN VALUE rst_any_method(VALUE);
|
1
ext/-test-/load/stringify_symbols/extconf.rb
Normal file
1
ext/-test-/load/stringify_symbols/extconf.rb
Normal file
@ -0,0 +1 @@
|
||||
create_makefile('-test-/load/stringify_symbols')
|
29
ext/-test-/load/stringify_symbols/stringify_symbols.c
Normal file
29
ext/-test-/load/stringify_symbols/stringify_symbols.c
Normal file
@ -0,0 +1,29 @@
|
||||
#include <ruby.h>
|
||||
#include "ruby/internal/intern/load.h"
|
||||
#include "ruby/util.h"
|
||||
|
||||
#if SIZEOF_INTPTR_T == SIZEOF_LONG_LONG
|
||||
# define UINTPTR2NUM ULL2NUM
|
||||
#elif SIZEOF_INTPTR_T == SIZEOF_LONG
|
||||
# define UINTPTR2NUM ULONG2NUM
|
||||
#else
|
||||
# define UINTPTR2NUM UINT2NUM
|
||||
#endif
|
||||
|
||||
static VALUE
|
||||
stringify_symbol(VALUE klass, VALUE fname, VALUE sname)
|
||||
{
|
||||
void *ptr = rb_ext_resolve_symbol(StringValueCStr(fname), StringValueCStr(sname));
|
||||
if (ptr == NULL) {
|
||||
return Qnil;
|
||||
}
|
||||
uintptr_t uintptr = (uintptr_t)ptr;
|
||||
return UINTPTR2NUM(uintptr);
|
||||
}
|
||||
|
||||
void
|
||||
Init_stringify_symbols(void)
|
||||
{
|
||||
VALUE mod = rb_define_module("StringifySymbols");
|
||||
rb_define_singleton_method(mod, "stringify_symbol", stringify_symbol, 2);
|
||||
}
|
1
ext/-test-/load/stringify_target/extconf.rb
Normal file
1
ext/-test-/load/stringify_target/extconf.rb
Normal file
@ -0,0 +1 @@
|
||||
create_makefile('-test-/load/stringify_target')
|
15
ext/-test-/load/stringify_target/stringify_target.c
Normal file
15
ext/-test-/load/stringify_target/stringify_target.c
Normal file
@ -0,0 +1,15 @@
|
||||
#include <ruby.h>
|
||||
#include "stringify_target.h"
|
||||
|
||||
VALUE
|
||||
stt_any_method(VALUE klass)
|
||||
{
|
||||
return rb_str_new_cstr("from target");
|
||||
}
|
||||
|
||||
void
|
||||
Init_stringify_target(void)
|
||||
{
|
||||
VALUE mod = rb_define_module("StringifyTarget");
|
||||
rb_define_singleton_method(mod, "any_method", stt_any_method, 0);
|
||||
}
|
4
ext/-test-/load/stringify_target/stringify_target.def
Normal file
4
ext/-test-/load/stringify_target/stringify_target.def
Normal file
@ -0,0 +1,4 @@
|
||||
LIBRARY stringify_target
|
||||
EXPORTS
|
||||
Init_stringify_target
|
||||
stt_any_method
|
4
ext/-test-/load/stringify_target/stringify_target.h
Normal file
4
ext/-test-/load/stringify_target/stringify_target.h
Normal file
@ -0,0 +1,4 @@
|
||||
#include <ruby.h>
|
||||
#include "ruby/internal/dllexport.h"
|
||||
|
||||
RUBY_EXTERN VALUE stt_any_method(VALUE);
|
@ -176,6 +176,43 @@ VALUE rb_f_require(VALUE self, VALUE feature);
|
||||
*/
|
||||
VALUE rb_require_string(VALUE feature);
|
||||
|
||||
/**
|
||||
* Resolves and returns a symbol of a function in the native extension
|
||||
* specified by the feature and symbol names. Extensions will use this function
|
||||
* to access the symbols provided by other native extensions.
|
||||
*
|
||||
* @param[in] feature Name of a feature, e.g. `"json"`.
|
||||
* @param[in] symbol Name of a symbol defined by the feature.
|
||||
* @return The resolved symbol of a function, defined and externed by the
|
||||
* specified feature. It may be NULL if the feature is not loaded,
|
||||
* the feature is not extension, or the symbol is not found.
|
||||
*/
|
||||
void *rb_ext_resolve_symbol(const char *feature, const char *symbol);
|
||||
|
||||
/**
|
||||
* This macro is to provide backwards compatibility. It provides a way to
|
||||
* define function prototypes and resolving function symbols in a safe way.
|
||||
*
|
||||
* ```CXX
|
||||
* // prototypes
|
||||
* #ifdef HAVE_RB_EXT_RESOLVE_SYMBOL
|
||||
* VALUE *(*other_extension_func)(VALUE,VALUE);
|
||||
* #else
|
||||
* VALUE other_extension_func(VALUE);
|
||||
* #endif
|
||||
*
|
||||
* // in Init_xxx()
|
||||
* #ifdef HAVE_RB_EXT_RESOLVE_SYMBOL
|
||||
* other_extension_func = \
|
||||
* (VALUE(*)(VALUE,VALUE))rb_ext_resolve_symbol(fname, sym_name);
|
||||
* if (other_extension_func == NULL) {
|
||||
* // raise your own error
|
||||
* }
|
||||
* #endif
|
||||
* ```
|
||||
*/
|
||||
#define HAVE_RB_EXT_RESOLVE_SYMBOL 1
|
||||
|
||||
/**
|
||||
* @name extension configuration
|
||||
* @{
|
||||
|
56
load.c
56
load.c
@ -8,6 +8,7 @@
|
||||
#include "internal/dir.h"
|
||||
#include "internal/error.h"
|
||||
#include "internal/file.h"
|
||||
#include "internal/hash.h"
|
||||
#include "internal/load.h"
|
||||
#include "internal/ruby_parser.h"
|
||||
#include "internal/thread.h"
|
||||
@ -18,12 +19,22 @@
|
||||
#include "ruby/encoding.h"
|
||||
#include "ruby/util.h"
|
||||
|
||||
static VALUE ruby_dln_librefs;
|
||||
static VALUE ruby_dln_libmap;
|
||||
|
||||
#define IS_RBEXT(e) (strcmp((e), ".rb") == 0)
|
||||
#define IS_SOEXT(e) (strcmp((e), ".so") == 0 || strcmp((e), ".o") == 0)
|
||||
#define IS_DLEXT(e) (strcmp((e), DLEXT) == 0)
|
||||
|
||||
#if SIZEOF_VALUE <= SIZEOF_LONG
|
||||
# define SVALUE2NUM(x) LONG2NUM((long)(x))
|
||||
# define NUM2SVALUE(x) (SIGNED_VALUE)NUM2LONG(x)
|
||||
#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG
|
||||
# define SVALUE2NUM(x) LL2NUM((LONG_LONG)(x))
|
||||
# define NUM2SVALUE(x) (SIGNED_VALUE)NUM2LL(x)
|
||||
#else
|
||||
# error Need integer for VALUE
|
||||
#endif
|
||||
|
||||
enum {
|
||||
loadable_ext_rb = (0+ /* .rb extension is the first in both tables */
|
||||
1) /* offset by rb_find_file_ext() */
|
||||
@ -1225,7 +1236,7 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa
|
||||
ec->errinfo = Qnil; /* ensure */
|
||||
th->top_wrapper = 0;
|
||||
if ((state = EC_EXEC_TAG()) == TAG_NONE) {
|
||||
long handle;
|
||||
VALUE handle;
|
||||
int found;
|
||||
|
||||
RUBY_DTRACE_HOOK(FIND_REQUIRE_ENTRY, RSTRING_PTR(fname));
|
||||
@ -1256,9 +1267,9 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa
|
||||
case 's':
|
||||
reset_ext_config = true;
|
||||
ext_config_push(th, &prev_ext_config);
|
||||
handle = (long)rb_vm_call_cfunc(rb_vm_top_self(), load_ext,
|
||||
path, VM_BLOCK_HANDLER_NONE, path);
|
||||
rb_ary_push(ruby_dln_librefs, LONG2NUM(handle));
|
||||
handle = rb_vm_call_cfunc(rb_vm_top_self(), load_ext,
|
||||
path, VM_BLOCK_HANDLER_NONE, path);
|
||||
rb_hash_aset(ruby_dln_libmap, path, SVALUE2NUM((SIGNED_VALUE)handle));
|
||||
break;
|
||||
}
|
||||
result = TAG_RETURN;
|
||||
@ -1518,6 +1529,37 @@ rb_f_autoload_p(int argc, VALUE *argv, VALUE obj)
|
||||
return rb_mod_autoload_p(argc, argv, klass);
|
||||
}
|
||||
|
||||
void *
|
||||
rb_ext_resolve_symbol(const char* fname, const char* symbol)
|
||||
{
|
||||
VALUE handle;
|
||||
VALUE resolved;
|
||||
VALUE path;
|
||||
char *ext;
|
||||
VALUE fname_str = rb_str_new_cstr(fname);
|
||||
|
||||
resolved = rb_resolve_feature_path((VALUE)NULL, fname_str);
|
||||
if (NIL_P(resolved)) {
|
||||
ext = strrchr(fname, '.');
|
||||
if (!ext || !IS_SOEXT(ext)) {
|
||||
rb_str_cat_cstr(fname_str, ".so");
|
||||
}
|
||||
if (rb_feature_p(GET_VM(), fname, 0, FALSE, FALSE, 0)) {
|
||||
return dln_symbol(NULL, symbol);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
if (RARRAY_LEN(resolved) != 2 || rb_ary_entry(resolved, 0) != ID2SYM(rb_intern("so"))) {
|
||||
return NULL;
|
||||
}
|
||||
path = rb_ary_entry(resolved, 1);
|
||||
handle = rb_hash_lookup(ruby_dln_libmap, path);
|
||||
if (NIL_P(handle)) {
|
||||
return NULL;
|
||||
}
|
||||
return dln_symbol((void *)NUM2SVALUE(handle), symbol);
|
||||
}
|
||||
|
||||
void
|
||||
Init_load(void)
|
||||
{
|
||||
@ -1552,6 +1594,6 @@ Init_load(void)
|
||||
rb_define_global_function("autoload", rb_f_autoload, 2);
|
||||
rb_define_global_function("autoload?", rb_f_autoload_p, -1);
|
||||
|
||||
ruby_dln_librefs = rb_ary_hidden_new(0);
|
||||
rb_gc_register_mark_object(ruby_dln_librefs);
|
||||
ruby_dln_libmap = rb_hash_new_with_size(0);
|
||||
rb_gc_register_mark_object(ruby_dln_libmap);
|
||||
}
|
||||
|
24
test/-ext-/load/test_resolve_symbol.rb
Normal file
24
test/-ext-/load/test_resolve_symbol.rb
Normal file
@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
require 'test/unit'
|
||||
|
||||
class Test_Load_ResolveSymbol < Test::Unit::TestCase
|
||||
def test_load_resolve_symbol_resolver
|
||||
feature = "Feature #20005"
|
||||
assert_raise(LoadError, "resolve_symbol_target is not loaded") {
|
||||
require '-test-/load/resolve_symbol_resolver'
|
||||
}
|
||||
require '-test-/load/resolve_symbol_target'
|
||||
assert_nothing_raised(LoadError, "#{feature} resolver can be loaded") {
|
||||
require '-test-/load/resolve_symbol_resolver'
|
||||
}
|
||||
assert_not_nil ResolveSymbolResolver
|
||||
assert_equal "from target", ResolveSymbolResolver.any_method
|
||||
|
||||
assert_raise(LoadError, "tries to resolve missing feature name, and it should raise LoadError") {
|
||||
ResolveSymbolResolver.try_resolve_fname
|
||||
}
|
||||
assert_raise(LoadError, "tries to resolve missing symbol name, and it should raise LoadError") {
|
||||
ResolveSymbolResolver.try_resolve_sname
|
||||
}
|
||||
end
|
||||
end
|
35
test/-ext-/load/test_stringify_symbols.rb
Normal file
35
test/-ext-/load/test_stringify_symbols.rb
Normal file
@ -0,0 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
require 'test/unit'
|
||||
|
||||
class Test_Load_stringify_symbols < Test::Unit::TestCase
|
||||
def test_load_stringify_symbol_required_extensions
|
||||
require '-test-/load/stringify_symbols'
|
||||
require '-test-/load/stringify_target'
|
||||
r1 = StringifySymbols.stringify_symbol("-test-/load/stringify_target", "stt_any_method")
|
||||
assert_not_nil r1
|
||||
r2 = StringifySymbols.stringify_symbol("-test-/load/stringify_target.so", "stt_any_method")
|
||||
assert_equal r1, r2, "resolved symbols should be equal even with or without .so suffix"
|
||||
end
|
||||
|
||||
def test_load_stringify_symbol_statically_linked
|
||||
require '-test-/load/stringify_symbols'
|
||||
# "complex.so" is actually not a statically linked extension.
|
||||
# But it is registered in $LOADED_FEATURES, so it can be a target of this test.
|
||||
r1 = StringifySymbols.stringify_symbol("complex", "rb_complex_minus")
|
||||
assert_not_nil r1
|
||||
r2 = StringifySymbols.stringify_symbol("complex.so", "rb_complex_minus")
|
||||
assert_equal r1, r2
|
||||
end
|
||||
|
||||
def test_load_stringify_symbol_missing_target
|
||||
require '-test-/load/stringify_symbols'
|
||||
r1 = assert_nothing_raised {
|
||||
StringifySymbols.stringify_symbol("something_missing", "unknown_method")
|
||||
}
|
||||
assert_nil r1
|
||||
r2 = assert_nothing_raised {
|
||||
StringifySymbols.stringify_symbol("complex.so", "unknown_method")
|
||||
}
|
||||
assert_nil r2
|
||||
end
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user