Skip to content

Commit d497bae

Browse files
committed
[GR-18163] Save the ABI version in native extensions and check it when loading a native extension
PullRequest: truffleruby/2735
2 parents 2d8ba91 + 25c5423 commit d497bae

File tree

11 files changed

+97
-39
lines changed

11 files changed

+97
-39
lines changed

lib/cext/ABI_version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3
1+
4

lib/cext/include/ruby/ruby.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@ extern "C" {
2424
// Must be first, as it defines feature test macros like _GNU_SOURCE,
2525
// which influences the definitions exposed by system header files.
2626
#include "ruby/config.h"
27-
#include <truffleruby/truffleruby-pre.h>
2827
#ifdef RUBY_EXTCONF_H
2928
#include RUBY_EXTCONF_H
3029
#endif
3130

3231
#include "defines.h"
3332
#include "ruby/assert.h"
3433

34+
#include <truffleruby/truffleruby-pre.h>
35+
3536
/* For MinGW, we need __declspec(dllimport) for RUBY_EXTERN on MJIT.
3637
mswin's RUBY_EXTERN already has that. See also: win32/Makefile.sub */
3738
#if defined(MJIT_HEADER) && defined(_WIN32) && defined(__GNUC__)

lib/cext/include/truffleruby/truffleruby-pre.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ POLYGLOT_DECLARE_TYPE(VALUE)
5353
extern void* rb_tr_cext;
5454
#define RUBY_CEXT rb_tr_cext
5555

56+
void* rb_tr_abi_version(void) __attribute__((weak));
57+
void* rb_tr_abi_version(void) {
58+
char* abi_version = STRINGIZE(TRUFFLERUBY_ABI_VERSION);
59+
return polyglot_from_string(abi_version, "US-ASCII");
60+
}
61+
5662
// Wrapping and unwrapping of values.
5763

5864
extern void* (*rb_tr_unwrap)(VALUE obj);

lib/truffle/rbconfig.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ module RbConfig
7373

7474
# Determine the various flags for native compilation
7575
optflags = ''
76-
debugflags = ''
76+
debugflags = "-DTRUFFLERUBY_ABI_VERSION=#{ruby_abi_version}"
7777
warnflags = [
7878
'-Wimplicit-function-declaration', # To make missing C ext functions clear
7979
'-Wno-int-conversion', # MRI has VALUE defined as long while we have it as void*

lib/truffle/truffle/cext.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,16 @@ def supported?
118118
Interop.mime_type_supported?('application/x-sulong-library')
119119
end
120120

121+
def check_abi_version(embedded_abi_version, extension_path)
122+
runtime_abi_version = Truffle::GemUtil.abi_version
123+
if embedded_abi_version != runtime_abi_version
124+
message = "The native extension at #{extension_path} has a different ABI version: #{embedded_abi_version.inspect} " \
125+
"than the running TruffleRuby: #{runtime_abi_version.inspect}"
126+
warn message, uplevel: 1
127+
raise LoadError, message
128+
end
129+
end
130+
121131
def rb_stdin
122132
$stdin
123133
end

src/main/c/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ TRUFFLE_POSIX := truffleposix/libtruffleposix.$(SOEXT)
2121
SPAWN_HELPER := spawn-helper/spawn-helper
2222

2323
RUBY_HEADERS := $(wildcard $(ROOT)/lib/cext/include/*.h) $(wildcard $(ROOT)/lib/cext/include/*/*.h) $(wildcard $(ROOT)/lib/cext/include/*/*/*.h)
24+
ABI_VERSION := $(ROOT)/lib/cext/ABI_version.txt
2425
RBCONFIG := $(ROOT)/lib/truffle/rbconfig.rb
2526
MKMF := $(ROOT)/lib/mri/mkmf.rb
2627
LIBTRUFFLERUBY = cext/libtruffleruby.$(SOEXT)
27-
BASIC_EXTCONF_DEPS := $(SPAWN_HELPER) $(TRUFFLE_POSIX) $(RUBY_HEADERS) $(RBCONFIG) $(MKMF)
28+
BASIC_EXTCONF_DEPS := $(SPAWN_HELPER) $(TRUFFLE_POSIX) $(RUBY_HEADERS) $(ABI_VERSION) $(RBCONFIG) $(MKMF)
2829
# C extensions link against libtruffleruby (and might do have_func() checks against it), so it needs to be there before.
2930
# However, if libtruffleruby is recompiled, there is no need to rebuild C extensions, so it's a order-only-prerequisite.
3031
EXTCONF_DEPS := $(BASIC_EXTCONF_DEPS) | $(LIBTRUFFLERUBY)

src/main/java/org/truffleruby/core/CoreLibrary.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ public class CoreLibrary {
184184
public final RubyClass truffleFFINullPointerErrorClass;
185185
public final RubyModule truffleTypeModule;
186186
public final RubyModule truffleModule;
187+
public final RubyModule truffleCExtModule;
187188
public final RubyModule truffleInternalModule;
188189
public final RubyModule truffleBootModule;
189190
public final RubyModule truffleExceptionOperationsModule;
@@ -489,7 +490,7 @@ public CoreLibrary(RubyContext context, RubyLanguage language) {
489490
truffleInteropModule,
490491
interopExceptionClass,
491492
"UnknownKeyException");
492-
defineModule(truffleModule, "CExt");
493+
truffleCExtModule = defineModule(truffleModule, "CExt");
493494
defineModule(truffleModule, "Debug");
494495
defineModule(truffleModule, "ObjSpace");
495496
defineModule(truffleModule, "Coverage");

src/main/java/org/truffleruby/language/loader/FeatureLoader.java

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@
2020
import java.util.Map;
2121
import java.util.concurrent.locks.ReentrantLock;
2222

23+
import com.oracle.truffle.api.interop.UnknownIdentifierException;
24+
import com.oracle.truffle.api.interop.UnsupportedMessageException;
2325
import org.jcodings.Encoding;
2426
import org.jcodings.specific.UTF8Encoding;
2527
import org.truffleruby.RubyContext;
2628
import org.truffleruby.RubyLanguage;
2729
import org.truffleruby.collections.ConcurrentOperations;
2830
import org.truffleruby.core.array.ArrayOperations;
31+
import org.truffleruby.core.array.ArrayUtils;
2932
import org.truffleruby.core.array.RubyArray;
3033
import org.truffleruby.core.encoding.EncodingManager;
3134
import org.truffleruby.core.module.RubyModule;
@@ -35,7 +38,9 @@
3538
import org.truffleruby.core.thread.RubyThread;
3639
import org.truffleruby.extra.TruffleRubyNodes;
3740
import org.truffleruby.extra.ffi.Pointer;
41+
import org.truffleruby.interop.InteropNodes;
3842
import org.truffleruby.interop.TranslateInteropExceptionNode;
43+
import org.truffleruby.language.Nil;
3944
import org.truffleruby.language.RubyConstant;
4045
import org.truffleruby.language.control.RaiseException;
4146
import org.truffleruby.language.dispatch.DispatchNode;
@@ -434,8 +439,7 @@ public void ensureCExtImplementationLoaded(String feature, RequireNode requireNo
434439
final String rubyLibPath = context.getRubyHome() + "/lib/cext/libtruffleruby" + Platform.LIB_SUFFIX;
435440
final Object library = loadCExtLibRuby(rubyLibPath, feature, requireNode);
436441

437-
final Object initFunction = requireNode
438-
.findFunctionInLibrary(library, "rb_tr_init", rubyLibPath);
442+
final Object initFunction = findFunctionInLibrary(library, "rb_tr_init", rubyLibPath);
439443

440444
final InteropLibrary interop = InteropLibrary.getFactory().getUncached();
441445
try {
@@ -479,12 +483,63 @@ public Object loadCExtLibrary(String feature, String path, Node currentNode) {
479483
final TruffleFile truffleFile = FileLoader.getSafeTruffleFile(context, path);
480484
FileLoader.ensureReadable(context, truffleFile, currentNode);
481485

482-
final Source source = Source.newBuilder("llvm", truffleFile).build();
483-
return context.getEnv().parseInternal(source).call();
484-
} catch (IOException e) {
485-
throw new RaiseException(context, context.getCoreExceptions().loadError(e, path, currentNode));
486+
final Source source;
487+
try {
488+
source = Source.newBuilder("llvm", truffleFile).build();
489+
} catch (IOException e) {
490+
throw new RaiseException(context, context.getCoreExceptions().loadError(e, path, currentNode));
491+
}
492+
493+
final Object library = context.getEnv().parseInternal(source).call();
494+
495+
final Object embeddedABIVersion = getEmbeddedABIVersion(path, library);
496+
RubyContext.send(context.getCoreLibrary().truffleCExtModule, "check_abi_version", embeddedABIVersion, path);
497+
498+
return library;
486499
} finally {
487500
Metrics.printTime("after-load-cext-" + feature);
488501
}
489502
}
503+
504+
private Object getEmbeddedABIVersion(String expandedPath, Object library) {
505+
if (!InteropLibrary.getFactory().getUncached(library).isMemberReadable(library, "rb_tr_abi_version")) {
506+
return Nil.INSTANCE;
507+
}
508+
509+
final Object abiVersionFunction = findFunctionInLibrary(library, "rb_tr_abi_version", expandedPath);
510+
final InteropLibrary abiFunctionInteropLibrary = InteropLibrary.getFactory().getUncached(abiVersionFunction);
511+
final String abiVersion = (String) InteropNodes.execute(
512+
abiVersionFunction,
513+
ArrayUtils.EMPTY_ARRAY,
514+
abiFunctionInteropLibrary,
515+
TranslateInteropExceptionNode.getUncached());
516+
return StringOperations.createString(
517+
context,
518+
language,
519+
StringOperations.encodeRope(abiVersion, UTF8Encoding.INSTANCE));
520+
}
521+
522+
Object findFunctionInLibrary(Object library, String functionName, String path) {
523+
final Object function;
524+
try {
525+
function = InteropLibrary.getFactory().getUncached(library).readMember(library, functionName);
526+
} catch (UnknownIdentifierException e) {
527+
throw new RaiseException(
528+
context,
529+
context.getCoreExceptions().loadError(String.format("%s() not found", functionName), path, null));
530+
} catch (UnsupportedMessageException e) {
531+
throw TranslateInteropExceptionNode.getUncached().execute(e);
532+
}
533+
534+
if (function == null) {
535+
throw new RaiseException(
536+
context,
537+
context.getCoreExceptions().loadError(
538+
String.format("%s() not found (READ returned null)", functionName),
539+
path,
540+
null));
541+
}
542+
543+
return function;
544+
}
490545
}

src/main/java/org/truffleruby/language/loader/RequireNode.java

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@
4444
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
4545
import com.oracle.truffle.api.dsl.Specialization;
4646
import com.oracle.truffle.api.interop.InteropLibrary;
47-
import com.oracle.truffle.api.interop.UnknownIdentifierException;
48-
import com.oracle.truffle.api.interop.UnsupportedMessageException;
4947
import com.oracle.truffle.api.nodes.IndirectCallNode;
5048
import com.oracle.truffle.api.nodes.Node;
5149
import com.oracle.truffle.api.source.SourceSection;
@@ -251,7 +249,6 @@ private void requireCExtension(String feature, String expandedPath, Node current
251249
final FeatureLoader featureLoader = getContext().getFeatureLoader();
252250

253251
final Object library;
254-
255252
try {
256253
featureLoader.ensureCExtImplementationLoaded(feature, this);
257254

@@ -267,10 +264,9 @@ private void requireCExtension(String feature, String expandedPath, Node current
267264
}
268265

269266
final String initFunctionName = "Init_" + getBaseName(expandedPath);
267+
final Object initFunction = featureLoader.findFunctionInLibrary(library, initFunctionName, expandedPath);
270268

271-
final Object initFunction = findFunctionInLibrary(library, initFunctionName, expandedPath);
272-
273-
InteropLibrary initFunctionInteropLibrary = InteropLibrary.getFactory().getUncached(initFunction);
269+
final InteropLibrary initFunctionInteropLibrary = InteropLibrary.getFactory().getUncached(initFunction);
274270
if (!initFunctionInteropLibrary.isExecutable(initFunction)) {
275271
throw new RaiseException(
276272
getContext(),
@@ -290,28 +286,6 @@ private void requireCExtension(String feature, String expandedPath, Node current
290286
}
291287
}
292288

293-
Object findFunctionInLibrary(Object library, String functionName, String path) {
294-
final Object function;
295-
try {
296-
function = InteropLibrary.getFactory().getUncached(library).readMember(library, functionName);
297-
} catch (UnknownIdentifierException e) {
298-
throw new RaiseException(
299-
getContext(),
300-
coreExceptions().loadError(String.format("%s() not found", functionName), path, null));
301-
} catch (UnsupportedMessageException e) {
302-
throw TranslateInteropExceptionNode.getUncached().execute(e);
303-
}
304-
305-
if (function == null) {
306-
throw new RaiseException(
307-
getContext(),
308-
coreExceptions()
309-
.loadError(String.format("%s() not found (READ returned null)", functionName), path, null));
310-
}
311-
312-
return function;
313-
}
314-
315289
@TruffleBoundary
316290
private void handleCExtensionException(String feature, Exception e) {
317291
TranslateExceptionNode.logJavaException(getContext(), this, e);

src/main/ruby/truffleruby/core/truffle/gem_util.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,15 @@ def self.abi_version
142142
@abi_version ||= "#{RUBY_VERSION}.#{Truffle::Boot.basic_abi_version}".freeze
143143
end
144144

145+
def self.check_abi_version(embedded_abi_version, extension_path)
146+
if embedded_abi_version != abi_version
147+
message = "The native extension at #{extension_path} has a different ABI version: #{embedded_abi_version.inspect} " \
148+
"than the running TruffleRuby: #{abi_version.inspect}"
149+
warn message, uplevel: 1
150+
raise LoadError, message
151+
end
152+
end
153+
145154
def self.expand(path)
146155
if File.directory?(path)
147156
File.realpath(path)

0 commit comments

Comments
 (0)