diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a13ff12 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +/.bundle/ +/.yardoc +Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +lib/mini_racer_extension.so +lib/mini_racer_loader.so +*.bundle diff --git a/.gitignore b/.gitignore index ac6d1c1..a13ff12 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ Gemfile.lock /spec/reports/ /tmp/ lib/mini_racer_extension.so +lib/mini_racer_loader.so *.bundle diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4cc9f70 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +ARG RUBY_VERSION=2.7 +FROM ruby:${RUBY_VERSION} + +# without this `COPY .git`, we get the following error: +# fatal: not a git repository (or any of the parent directories): .git +# but with it we need the full gem just to compile the extension because +# of gemspec's `git --ls-files` +# COPY .git /code/.git +COPY Gemfile mini_racer.gemspec /code/ +COPY lib/mini_racer/version.rb /code/lib/mini_racer/version.rb +WORKDIR /code +RUN bundle install + +COPY Rakefile /code/ +COPY ext /code/ext/ +RUN bundle exec rake compile + +COPY . /code/ +CMD bundle exec irb -rmini_racer diff --git a/Rakefile b/Rakefile index e3b92ee..d125bf7 100644 --- a/Rakefile +++ b/Rakefile @@ -11,6 +11,7 @@ end task :default => [:compile, :test] gem = Gem::Specification.load( File.dirname(__FILE__) + '/mini_racer.gemspec' ) +Rake::ExtensionTask.new( 'mini_racer_loader', gem ) Rake::ExtensionTask.new( 'mini_racer_extension', gem ) diff --git a/ext/mini_racer_extension/extconf.rb b/ext/mini_racer_extension/extconf.rb index 7ef61b9..9e60dbe 100644 --- a/ext/mini_racer_extension/extconf.rb +++ b/ext/mini_racer_extension/extconf.rb @@ -14,6 +14,7 @@ $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or IS_DARWIN $CPPFLAGS += " -std=c++0x" $CPPFLAGS += " -fpermissive" $CPPFLAGS += " -DV8_COMPRESS_POINTERS" +$CPPFLAGS += " -fvisibility=hidden " $CPPFLAGS += " -Wno-reserved-user-defined-literal" if IS_DARWIN diff --git a/ext/mini_racer_extension/mini_racer_extension.cc b/ext/mini_racer_extension/mini_racer_extension.cc index 9414407..fd75768 100644 --- a/ext/mini_racer_extension/mini_racer_extension.cc +++ b/ext/mini_racer_extension/mini_racer_extension.cc @@ -1658,7 +1658,7 @@ static void set_ruby_exiting(VALUE value) { extern "C" { - void Init_mini_racer_extension ( void ) + __attribute__((visibility("default"))) void Init_mini_racer_extension ( void ) { VALUE rb_mMiniRacer = rb_define_module("MiniRacer"); rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject); diff --git a/ext/mini_racer_loader/extconf.rb b/ext/mini_racer_loader/extconf.rb new file mode 100644 index 0000000..e8eaf22 --- /dev/null +++ b/ext/mini_racer_loader/extconf.rb @@ -0,0 +1,8 @@ +require 'mkmf' + +extension_name = 'mini_racer_loader' +dir_config extension_name + +$CPPFLAGS += " -fvisibility=hidden " + +create_makefile extension_name diff --git a/ext/mini_racer_loader/mini_racer_loader.c b/ext/mini_racer_loader/mini_racer_loader.c new file mode 100644 index 0000000..0516275 --- /dev/null +++ b/ext/mini_racer_loader/mini_racer_loader.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include + +// Load a Ruby extension like Ruby does, only with flags that: +// a) hide symbols from other extensions (RTLD_LOCAL) +// b) bind symbols tightly (RTLD_DEEPBIND, when available) + +void Init_mini_racer_loader(void); + +static void *_dln_load(const char *file); + +static VALUE _load_shared_lib(VALUE self, volatile VALUE fname) +{ + (void) self; + + // check that path is not tainted + SafeStringValue(fname); + + FilePathValue(fname); + VALUE path = rb_str_encode_ospath(fname); + + char *loc = StringValueCStr(path); + void *handle = _dln_load(loc); + + return handle ? Qtrue : Qfalse; +} + +// adapted from Ruby's dln.c +#define INIT_FUNC_PREFIX ((char[]) {'I', 'n', 'i', 't', '_'}) +#define INIT_FUNCNAME(buf, file) do { \ + const char *base = (file); \ + const size_t flen = _init_funcname(&base); \ + const size_t plen = sizeof(INIT_FUNC_PREFIX); \ + char *const tmp = ALLOCA_N(char, plen + flen + 1); \ + memcpy(tmp, INIT_FUNC_PREFIX, plen); \ + memcpy(tmp+plen, base, flen); \ + tmp[plen+flen] = '\0'; \ + *(buf) = tmp; \ +} while(0) + +// adapted from Ruby's dln.c +static size_t _init_funcname(const char **file) +{ + const char *p = *file, + *base, + *dot = NULL; + + for (base = p; *p; p++) { /* Find position of last '/' */ + if (*p == '.' && !dot) { + dot = p; + } + if (*p == '/') { + base = p + 1; + dot = NULL; + } + } + *file = base; + return (uintptr_t) ((dot ? dot : p) - base); +} + +// adapted from Ruby's dln.c +static void *_dln_load(const char *file) +{ + char *buf; + const char *error; +#define DLN_ERROR() (error = dlerror(), strcpy(ALLOCA_N(char, strlen(error) + 1), error)) + + void *handle; + void (*init_fct)(void); + + INIT_FUNCNAME(&buf, file); + +#ifndef RTLD_DEEPBIND +# define RTLD_DEEPBIND 0 +#endif + /* Load file */ + if ((handle = dlopen(file, RTLD_LAZY|RTLD_LOCAL|RTLD_DEEPBIND)) == NULL) { + DLN_ERROR(); + goto failed; + } +#if defined(RUBY_EXPORT) + { + static const char incompatible[] = "incompatible library version"; + void *ex = dlsym(handle, "ruby_xmalloc"); + if (ex && ex != (void *) &ruby_xmalloc) { + +# if defined __APPLE__ + /* dlclose() segfaults */ + rb_fatal("%s - %s", incompatible, file); +# else + dlclose(handle); + error = incompatible; + goto failed; +#endif + } + } +# endif + + init_fct = (void (*)(void)) dlsym(handle, buf); + if (init_fct == NULL) { + error = DLN_ERROR(); + dlclose(handle); + goto failed; + } + + /* Call the init code */ + (*init_fct)(); + + return handle; + +failed: + rb_raise(rb_eLoadError, "%s", error); +} + +__attribute__((visibility("default"))) void Init_mini_racer_loader() +{ + VALUE mMiniRacer = rb_define_module("MiniRacer"); + VALUE mLoader = rb_define_module_under(mMiniRacer, "Loader"); + rb_define_singleton_method(mLoader, "load", _load_shared_lib, 1); +} diff --git a/lib/mini_racer.rb b/lib/mini_racer.rb index 2e62e46..9190cea 100644 --- a/lib/mini_racer.rb +++ b/lib/mini_racer.rb @@ -1,5 +1,15 @@ require "mini_racer/version" -require "mini_racer_extension" +require "mini_racer_loader" +require "pathname" + +ext_filename = "mini_racer_extension.#{RbConfig::CONFIG['DLEXT']}" +ext_path = Gem.loaded_specs['mini_racer'].require_paths + .map { |p| (p = Pathname.new(p)).absolute? ? p : Pathname.new(__dir__).parent + p } +ext_found = ext_path.map { |p| p + ext_filename }.find { |p| p.file? } + +raise LoadError, "Could not find #{ext_filename} in #{ext_path.map(&:to_s)}" unless ext_found +MiniRacer::Loader.load(ext_found.to_s) + require "thread" require "json" diff --git a/mini_racer.gemspec b/mini_racer.gemspec index 9f1243c..071cd85 100644 --- a/mini_racer.gemspec +++ b/mini_racer.gemspec @@ -35,7 +35,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'libv8', MiniRacer::LIBV8_VERSION spec.require_paths = ["lib", "ext"] - spec.extensions = ["ext/mini_racer_extension/extconf.rb"] + spec.extensions = ["ext/mini_racer_loader/extconf.rb", "ext/mini_racer_extension/extconf.rb"] spec.required_ruby_version = '>= 2.3' end