Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emit HPy ABI version into the binary and check it when loading a module #387

Merged
merged 5 commits into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions hpy/devel/abitag.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

# NOTE: these must be kept on sync with the equivalent defines in hpy.h
HPY_ABI_VERSION = 0
HPY_ABI_VERSION_MINOR = 0
HPY_ABI_TAG = 'hpy%d' % HPY_ABI_VERSION

def parse_ext_suffix(ext_suffix=None):
Expand Down
8 changes: 8 additions & 0 deletions hpy/devel/include/hpy.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ extern "C" {

/* ~~~~~~~~~~~~~~~~ HPy ABI version ~~~~~~~~~~~~~~~ */
// NOTE: these must be kept on sync with the equivalent variables in hpy/devel/abitag.py
/**
* The ABI version.
*
* Minor version N+1 is binary compatible to minor version N. Major versions
* are not binary compatible (note: HPy can run several binary incompatible
* versions in one process).
*/
#define HPY_ABI_VERSION 0
#define HPY_ABI_VERSION_MINOR 0
#define HPY_ABI_TAG "hpy0"


Expand Down
40 changes: 26 additions & 14 deletions hpy/devel/include/hpy/hpymodule.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,33 +32,45 @@ typedef struct {


#if defined(__cplusplus)
# define HPyVERSION_FUNC extern "C" HPy_EXPORTED_SYMBOL uint32_t
# define HPyMODINIT_FUNC extern "C" HPy_EXPORTED_SYMBOL HPy
#else /* __cplusplus */
# define HPyVERSION_FUNC HPy_EXPORTED_SYMBOL uint32_t
# define HPyMODINIT_FUNC HPy_EXPORTED_SYMBOL HPy
#endif /* __cplusplus */

#ifdef HPY_ABI_CPYTHON

// module initialization in the CPython case
#define HPy_MODINIT(modname) \
static HPy init_##modname##_impl(HPyContext *ctx); \
PyMODINIT_FUNC \
PyInit_##modname(void) \
{ \
return _h2py(init_##modname##_impl(_HPyGetContext())); \
#define HPy_MODINIT(modname) \
static HPy init_##modname##_impl(HPyContext *ctx); \
PyMODINIT_FUNC \
PyInit_##modname(void) \
{ \
return _h2py(init_##modname##_impl(_HPyGetContext())); \
}

#else // HPY_ABI_CPYTHON

// module initialization in the universal and hybrid case
#define HPy_MODINIT(modname) \
_HPy_CTX_MODIFIER HPyContext *_ctx_for_trampolines; \
static HPy init_##modname##_impl(HPyContext *ctx); \
HPyMODINIT_FUNC \
HPyInit_##modname(HPyContext *ctx) \
{ \
_ctx_for_trampolines = ctx; \
return init_##modname##_impl(ctx); \
#define HPy_MODINIT(modname) \
HPyVERSION_FUNC \
get_required_hpy_major_version_##modname() \
{ \
return HPY_ABI_VERSION; \
} \
HPyVERSION_FUNC \
get_required_hpy_minor_version_##modname() \
{ \
return HPY_ABI_VERSION_MINOR; \
} \
_HPy_CTX_MODIFIER HPyContext *_ctx_for_trampolines; \
static HPy init_##modname##_impl(HPyContext *ctx); \
HPyMODINIT_FUNC \
HPyInit_##modname(HPyContext *ctx) \
{ \
_ctx_for_trampolines = ctx; \
return init_##modname##_impl(ctx); \
}

#endif // HPY_ABI_CPYTHON
Expand Down
68 changes: 68 additions & 0 deletions hpy/universal/src/hpymodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <strings.h>
#endif
#include <stdio.h>
#include <inttypes.h>


#include "api.h"
Expand Down Expand Up @@ -120,6 +121,36 @@ get_encoded_name(PyObject *name) {
return NULL;
}

static bool validate_abi_tag(const char *shortname, const char *soname,
uint32_t required_major_version, uint32_t required_minor_version) {
char *substr = strstr(soname, ".hpy");
if (substr != NULL) {
substr += strlen(".hpy");
if (*substr >= '0' && *substr <= '9') {
// It is a number w/o sign and whitespace, we can now parse it with atoi
uint32_t abi_tag = (uint32_t) atoi(substr);
if (abi_tag == required_major_version) {
return true;
}
PyErr_Format(PyExc_RuntimeError,
"HPy extension module '%s' at path '%s': mismatch between the "
"HPy ABI tag encoded in the filename and the major version requested "
"by the HPy extension itself. Major version tag parsed from "
"filename: %" PRIu32 ". Requested version: %" PRIu32 ".%" PRIu32 ".",
shortname, soname, abi_tag, required_major_version, required_minor_version);
return false;
}
}
PyErr_Format(PyExc_RuntimeError,
"HPy extension module '%s' at path '%s': could not find "
"HPy ABI tag encoded in the filename. The extension claims to be compiled with "
"HPy ABI version: %" PRIu32 ".%" PRIu32 ".",
shortname, soname, required_major_version, required_minor_version);
return false;
}

typedef uint32_t (*version_getter)(void);

static PyObject *do_load(PyObject *name_unicode, PyObject *path, HPyMode mode)
{
PyObject *name = NULL;
Expand Down Expand Up @@ -150,6 +181,43 @@ static PyObject *do_load(PyObject *name_unicode, PyObject *path, HPyMode mode)
goto error;
}

char minor_version_symbol_name[258];
char major_version_symbol_name[258];
PyOS_snprintf(minor_version_symbol_name, sizeof(init_name),
"get_required_hpy_minor_version_%.200s", shortname);
PyOS_snprintf(major_version_symbol_name, sizeof(init_name),
"get_required_hpy_major_version_%.200s", shortname);
void *minor_version_ptr = dlsym(mylib, minor_version_symbol_name);
void *major_version_ptr = dlsym(mylib, major_version_symbol_name);
if (minor_version_ptr == NULL || major_version_ptr == NULL) {
const char *error = dlerror();
if (error == NULL)
error = "no error message provided by the system";
PyErr_Format(PyExc_RuntimeError,
"Error during loading of the HPy extension module at path "
"'%s'. Cannot locate the required minimal HPy versions as symbols '%s' and `%s`. "
"Error message from dlopen/WinAPI: %s",
soname, minor_version_symbol_name, major_version_symbol_name, error);
goto error;
}
uint32_t required_minor_version = ((version_getter) minor_version_ptr)();
uint32_t required_major_version = ((version_getter) major_version_ptr)();
if (required_major_version != HPY_ABI_VERSION || required_minor_version > HPY_ABI_VERSION_MINOR) {
// For now, we have only one major version, but in the future at this
// point we would decide which HPyContext to create
PyErr_Format(PyExc_RuntimeError,
"HPy extension module '%s' requires unsupported version of the HPy runtime. "
"Requested version: %" PRIu32 ".%" PRIu32 ". Current HPy version: %" PRIu32 ".%" PRIu32 ".",
shortname, required_major_version, required_minor_version,
HPY_ABI_VERSION, HPY_ABI_VERSION_MINOR);
goto error;
}

// Sanity check of the tag in the shared object filename
if (!validate_abi_tag(shortname, soname, required_major_version, required_minor_version)) {
goto error;
}

void *initfn = dlsym(mylib, init_name);
if (initfn == NULL) {
const char *error = dlerror();
Expand Down
53 changes: 53 additions & 0 deletions test/test_00_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"fake pytest module")
"""
from .support import HPyTest
from hpy.devel.abitag import HPY_ABI_VERSION, HPY_ABI_VERSION_MINOR
import shutil


class TestBasic(HPyTest):
Expand All @@ -28,6 +30,57 @@ def test_empty_module(self):
""")
assert type(mod) is type(sys)

def test_abi_version_check(self):
if self.compiler.hpy_abi != 'universal':
return
try:
self.make_module("""
// hack: we redefine the version
#undef HPY_ABI_VERSION
#define HPY_ABI_VERSION 999
@INIT
""")
except RuntimeError as ex:
assert str(ex) == "HPy extension module 'mytest' requires unsupported " \
"version of the HPy runtime. Requested version: 999.0. " \
"Current HPy version: {}.{}.".format(HPY_ABI_VERSION, HPY_ABI_VERSION_MINOR)
else:
assert False, "Expected exception"

def test_abi_tag_check(self):
if self.compiler.hpy_abi != 'universal':
return

from hpy.universal import MODE_UNIVERSAL
def assert_load_raises(filename, message):
try:
self.compiler.load_universal_module('mytest', filename, mode=MODE_UNIVERSAL)
except RuntimeError as ex:
assert str(ex) == message
else:
assert False, "Expected exception"

module = self.compile_module("@INIT")
filename = module.so_filename
hpy_tag = ".hpy{}".format(HPY_ABI_VERSION)

filename_wrong_tag = filename.replace(hpy_tag, ".hpy999")
shutil.move(filename, filename_wrong_tag)
assert_load_raises(filename_wrong_tag,
"HPy extension module 'mytest' at path '{}': mismatch "
"between the HPy ABI tag encoded in the filename and "
"the major version requested by the HPy extension itself. "
"Major version tag parsed from filename: 999. "
"Requested version: {}.{}.".format(filename_wrong_tag, HPY_ABI_VERSION, HPY_ABI_VERSION_MINOR))

filename_no_tag = filename.replace(hpy_tag, "")
shutil.move(filename_wrong_tag, filename_no_tag)
assert_load_raises(filename_no_tag,
"HPy extension module 'mytest' at path '{}': "
"could not find HPy ABI tag encoded in the filename. "
"The extension claims to be compiled with HPy ABI version: "
"{}.{}.".format(filename_no_tag, HPY_ABI_VERSION, HPY_ABI_VERSION_MINOR))

def test_different_name(self):
mod = self.make_module("""
@INIT
Expand Down