Skip to content

Commit

Permalink
Merge pull request #387 from steve-s/ss/hpy-abi-version-in-abi
Browse files Browse the repository at this point in the history
Emit HPy ABI version into the binary and check it when loading a module
  • Loading branch information
mattip authored Dec 7, 2022
2 parents c7b51e7 + e70eff8 commit 10f222e
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 14 deletions.
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

0 comments on commit 10f222e

Please sign in to comment.