summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dln.c17
-rw-r--r--ext/-test-/abi/abi.c11
-rw-r--r--ext/-test-/abi/extconf.rb3
-rw-r--r--include/ruby/internal/abi.h45
-rw-r--r--include/ruby/ruby.h1
-rw-r--r--test/-ext-/test_abi.rb43
-rwxr-xr-xtool/mkconfig.rb9
-rw-r--r--tool/transform_mjit_header.rb1
8 files changed, 130 insertions, 0 deletions
diff --git a/dln.c b/dln.c
index 3ebce48a45..a38ff7341d 100644
--- a/dln.c
+++ b/dln.c
@@ -426,12 +426,29 @@ dln_sym(void *handle, const char *symbol)
}
#endif
+#if RUBY_DLN_CHECK_ABI
+static bool
+abi_check_enabled_p(void)
+{
+ const char *val = getenv("RUBY_ABI_CHECK");
+ return val == NULL || !(val[0] == '0' && val[1] == '\0');
+}
+#endif
+
void *
dln_load(const char *file)
{
#if defined(_WIN32) || defined(USE_DLN_DLOPEN)
void *handle = dln_open(file);
+#if RUBY_DLN_CHECK_ABI
+ unsigned long long (*abi_version_fct)(void) = (unsigned long long(*)(void))dln_sym(handle, "ruby_abi_version");
+ unsigned long long binary_abi_version = (*abi_version_fct)();
+ if (binary_abi_version != ruby_abi_version() && abi_check_enabled_p()) {
+ dln_loaderror("ABI version of binary is incompatible with this Ruby. Try rebuilding this binary.");
+ }
+#endif
+
char *init_fct_name;
init_funcname(&init_fct_name, file);
void (*init_fct)(void) = (void(*)(void))dln_sym(handle, init_fct_name);
diff --git a/ext/-test-/abi/abi.c b/ext/-test-/abi/abi.c
new file mode 100644
index 0000000000..923e0f67b8
--- /dev/null
+++ b/ext/-test-/abi/abi.c
@@ -0,0 +1,11 @@
+#include <limits.h>
+
+unsigned long long
+ruby_abi_version(void)
+{
+ return ULONG_MAX;
+}
+
+void
+Init_abi(void)
+{}
diff --git a/ext/-test-/abi/extconf.rb b/ext/-test-/abi/extconf.rb
new file mode 100644
index 0000000000..d786b15db9
--- /dev/null
+++ b/ext/-test-/abi/extconf.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: false
+require_relative "../auto_ext.rb"
+auto_ext(inc: true)
diff --git a/include/ruby/internal/abi.h b/include/ruby/internal/abi.h
new file mode 100644
index 0000000000..78ed4c1875
--- /dev/null
+++ b/include/ruby/internal/abi.h
@@ -0,0 +1,45 @@
+#ifndef RUBY_ABI_H
+#define RUBY_ABI_H
+
+/* This number represents Ruby's ABI version.
+ *
+ * In development Ruby, it should be bumped every time an ABI incompatible
+ * change is introduced. This will force other developers to rebuild extension
+ * gems.
+ *
+ * The following cases are considered as ABI incompatible changes:
+ * - Changing any data structures.
+ * - Changing macros or inline functions causing a change in behavior.
+ * - Deprecating or removing function declarations.
+ *
+ * The following cases are NOT considered as ABI incompatible changes:
+ * - Any changes that does not involve the header files in the `include`
+ * directory.
+ * - Adding macros, inline functions, or function declarations.
+ * - Backwards compatible refactors.
+ * - Editing comments.
+ *
+ * In released versions of Ruby, this number should not be changed since teeny
+ * versions of Ruby should guarantee ABI compatibility.
+ */
+#define RUBY_ABI_VERSION 0
+
+/* Windows does not support weak symbols so ruby_abi_version will not exist
+ * in the shared library. */
+#if defined(HAVE_FUNC_WEAK) && !defined(_WIN32) && !defined(__MINGW32__)
+# define RUBY_DLN_CHECK_ABI 1
+#else
+# define RUBY_DLN_CHECK_ABI 0
+#endif
+
+#if RUBY_DLN_CHECK_ABI
+
+RUBY_FUNC_EXPORTED unsigned long long __attribute__((weak))
+ruby_abi_version(void)
+{
+ return RUBY_ABI_VERSION;
+}
+
+#endif
+
+#endif
diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h
index f35d13685c..108127a93c 100644
--- a/include/ruby/ruby.h
+++ b/include/ruby/ruby.h
@@ -23,6 +23,7 @@
#include <stdarg.h>
#include "defines.h"
+#include "ruby/internal/abi.h"
#include "ruby/internal/anyargs.h"
#include "ruby/internal/arithmetic.h"
#include "ruby/internal/core.h"
diff --git a/test/-ext-/test_abi.rb b/test/-ext-/test_abi.rb
new file mode 100644
index 0000000000..ec2050ecad
--- /dev/null
+++ b/test/-ext-/test_abi.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+class TestABI < Test::Unit::TestCase
+ def test_require_lib_with_incorrect_abi_on_dev_ruby
+ omit "ABI is not checked" unless abi_checking_supported?
+
+ assert_separately [], <<~RUBY
+ err = assert_raise(LoadError) { require "-test-/abi" }
+ assert_match(/ABI version of binary is incompatible with this Ruby/, err.message)
+ RUBY
+ end
+
+ def test_disable_abi_check_using_environment_variable
+ omit "ABI is not checked" unless abi_checking_supported?
+
+ assert_separately [{ "RUBY_ABI_CHECK" => "0" }], <<~RUBY
+ assert_nothing_raised { require "-test-/abi" }
+ RUBY
+ end
+
+ def test_enable_abi_check_using_environment_variable
+ omit "ABI is not checked" unless abi_checking_supported?
+
+ assert_separately [{ "RUBY_ABI_CHECK" => "1" }], <<~RUBY
+ err = assert_raise(LoadError) { require "-test-/abi" }
+ assert_match(/ABI version of binary is incompatible with this Ruby/, err.message)
+ RUBY
+ end
+
+ def test_require_lib_with_incorrect_abi_on_release_ruby
+ omit "ABI is enforced" if abi_checking_supported?
+
+ assert_separately [], <<~RUBY
+ assert_nothing_raised { require "-test-/abi" }
+ RUBY
+ end
+
+ private
+
+ def abi_checking_supported?
+ !(RUBY_PLATFORM =~ /mswin|mingw/)
+ end
+end
diff --git a/tool/mkconfig.rb b/tool/mkconfig.rb
index 6e23af5185..0dd25eb400 100755
--- a/tool/mkconfig.rb
+++ b/tool/mkconfig.rb
@@ -229,6 +229,15 @@ end
print " CONFIG[#{v.dump}] = #{(versions[v]||vars[v]).dump}\n"
end
+# Get the ABI version
+File.foreach(File.join(srcdir, "include/ruby/internal/abi.h")) do |l|
+ m = /^\s*#\s*define\s+RUBY_ABI_VERSION\s+(\d+)/.match(l)
+ if m
+ print " CONFIG[\"ruby_abi_version\"] = \"#{m[1]}\"\n"
+ break
+ end
+end
+
dest = drive ? %r'= "(?!\$[\(\{])(?i:[a-z]:)' : %r'= "(?!\$[\(\{])'
v_disabled = {}
v_others.collect! do |x|
diff --git a/tool/transform_mjit_header.rb b/tool/transform_mjit_header.rb
index 2359ceab7c..8867c556f0 100644
--- a/tool/transform_mjit_header.rb
+++ b/tool/transform_mjit_header.rb
@@ -41,6 +41,7 @@ module MJITHeader
IGNORED_FUNCTIONS = [
'rb_vm_search_method_slowpath', # This increases the time to compile when inlined. So we use it as external function.
'rb_equal_opt', # Not used from VM and not compilable
+ 'ruby_abi_version',
]
ALWAYS_INLINED_FUNCTIONS = [