From 7f51e0ab9eb0570fad4dd90a798b9fd79d52e7a4 Mon Sep 17 00:00:00 2001 From: Vicente Eduardo Ferrer Garcia Date: Fri, 15 Nov 2024 01:03:14 +0100 Subject: [PATCH] Solve bug in c loader, improved python port add reference and dereference APIs in reflect. --- .../loaders/c_loader/source/c_loader_impl.cpp | 6 +- .../include/py_loader/py_loader_impl.h | 2 + .../loaders/py_loader/source/py_loader_impl.c | 40 ++-- .../loaders/py_loader/source/py_loader_port.c | 186 ++++++++++++++++++ .../include/metacall/metacall_value.h | 42 ++++ source/metacall/source/metacall_value.c | 10 + source/ports/py_port/metacall/__init__.py | 2 +- source/ports/py_port/metacall/api.py | 15 ++ .../reflect/include/reflect/reflect_value.h | 12 ++ .../include/reflect/reflect_value_type.h | 42 ++++ source/reflect/source/reflect_value.c | 18 ++ source/reflect/source/reflect_value_type.c | 14 ++ source/scripts/c/loadtest/source/loadtest.cpp | 5 + source/scripts/c/loadtest/source/loadtest.h | 2 + .../python/pointer/source/pointer.py.in | 15 ++ .../source/metacall_python_pointer_test.cpp | 31 +++ .../CMakeLists.txt | 152 ++++++++++++++ .../source/main.cpp | 28 +++ .../metacall_python_port_pointer_test.cpp | 109 ++++++++++ 19 files changed, 712 insertions(+), 19 deletions(-) create mode 100644 source/tests/metacall_python_port_pointer_test/CMakeLists.txt create mode 100644 source/tests/metacall_python_port_pointer_test/source/main.cpp create mode 100644 source/tests/metacall_python_port_pointer_test/source/metacall_python_port_pointer_test.cpp diff --git a/source/loaders/c_loader/source/c_loader_impl.cpp b/source/loaders/c_loader/source/c_loader_impl.cpp index d361c90c6..97db3fcc6 100644 --- a/source/loaders/c_loader/source/c_loader_impl.cpp +++ b/source/loaders/c_loader/source/c_loader_impl.cpp @@ -826,9 +826,9 @@ int c_loader_impl_initialize_types(loader_impl impl) { TYPE_FLOAT, "float" }, { TYPE_DOUBLE, "double" }, - { TYPE_INVALID, "void" } + { TYPE_NULL, "void" } - /* TODO: Do more types (and the unsigned versions too?) */ + /* TODO: Do more types */ }; size_t size = sizeof(type_id_name_pair) / sizeof(type_id_name_pair[0]); @@ -956,7 +956,7 @@ static type_id c_loader_impl_clang_type(loader_impl impl, CXCursor cursor, CXTyp return TYPE_INT; case CXType_Void: - return TYPE_INVALID; + return TYPE_NULL; case CXType_Enum: { CXCursor referenced = clang_isReference(cursor.kind) ? clang_getCursorReferenced(cursor) : cursor; diff --git a/source/loaders/py_loader/include/py_loader/py_loader_impl.h b/source/loaders/py_loader/include/py_loader/py_loader_impl.h index 7ec76f33a..efa286a6e 100644 --- a/source/loaders/py_loader/include/py_loader/py_loader_impl.h +++ b/source/loaders/py_loader/include/py_loader/py_loader_impl.h @@ -60,6 +60,8 @@ PY_LOADER_NO_EXPORT PyObject *py_loader_impl_value_to_capi(loader_impl impl, typ PY_LOADER_NO_EXPORT int py_loader_impl_finalizer_object(loader_impl impl, PyObject *obj, value v); +PY_LOADER_NO_EXPORT PyObject *py_loader_impl_capsule_new_null(void); + #ifdef __cplusplus } #endif diff --git a/source/loaders/py_loader/source/py_loader_impl.c b/source/loaders/py_loader/source/py_loader_impl.c index 556ba0aa8..66be1c0b9 100644 --- a/source/loaders/py_loader/source/py_loader_impl.c +++ b/source/loaders/py_loader/source/py_loader_impl.c @@ -221,6 +221,9 @@ static char *py_loader_impl_main_module = NULL; /* Holds reference to the original PyCFunction.tp_dealloc method */ static void (*py_loader_impl_pycfunction_dealloc)(PyObject *) = NULL; +/* Implements PyCapsules with null value internally */ +static const char py_loader_capsule_null_id[] = "__metacall_capsule_null__"; + PyObject *py_loader_impl_finalizer_object_impl(PyObject *self, PyObject *Py_UNUSED(args)) { value v = PyCapsule_GetPointer(self, NULL); @@ -273,6 +276,14 @@ int py_loader_impl_finalizer_object(loader_impl impl, PyObject *obj, value v) return 0; } +PyObject *py_loader_impl_capsule_new_null(void) +{ + /* We want to create a new capsule with contents set to NULL, but PyCapsule + * does not allow that, instead we are going to identify our NULL capsule with + * this configuration (setting the capsule to Py_None) */ + return PyCapsule_New(Py_None, py_loader_capsule_null_id, NULL); +} + void py_loader_impl_value_invoke_state_finalize(value v, void *data) { PyObject *capsule = (PyObject *)data; @@ -1142,17 +1153,17 @@ value py_loader_impl_capi_to_value(loader_impl impl, PyObject *obj, type_id id) } else if (id == TYPE_PTR) { - void *ptr = NULL; - -#if PY_MAJOR_VERSION == 2 + const char *name = PyCapsule_GetName(obj); + void *ptr = PyCapsule_GetPointer(obj, name); - /* TODO */ - -#elif PY_MAJOR_VERSION == 3 - ptr = PyCapsule_GetPointer(obj, NULL); - - v = value_create_ptr(ptr); -#endif + if (ptr == Py_None && name == py_loader_capsule_null_id) + { + v = value_create_ptr(NULL); + } + else + { + v = value_create_ptr(ptr); + } } else if (id == TYPE_FUNCTION) { @@ -1440,13 +1451,12 @@ PyObject *py_loader_impl_value_to_capi(loader_impl impl, type_id id, value v) { void *ptr = value_to_ptr(v); -#if PY_MAJOR_VERSION == 2 - - /* TODO */ + if (ptr == NULL) + { + return py_loader_impl_capsule_new_null(); + } -#elif PY_MAJOR_VERSION == 3 return PyCapsule_New(ptr, NULL, NULL); -#endif } else if (id == TYPE_FUTURE) { diff --git a/source/loaders/py_loader/source/py_loader_port.c b/source/loaders/py_loader/source/py_loader_port.c index 136635c31..a9cb3aa41 100644 --- a/source/loaders/py_loader/source/py_loader_port.c +++ b/source/loaders/py_loader/source/py_loader_port.c @@ -493,6 +493,7 @@ static PyObject *py_loader_port_invoke(PyObject *self, PyObject *var_args) /* Obtain Python loader implementation */ impl = loader_get_impl(py_loader_tag); + /* TODO: Remove this check when we implement this: https://github.com/metacall/core/issues/231 */ if (impl == NULL) { PyErr_SetString(PyExc_ValueError, "Invalid Python loader instance, MetaCall Port must be used from MetaCall CLI"); @@ -622,6 +623,7 @@ static PyObject *py_loader_port_await(PyObject *self, PyObject *var_args) /* Obtain Python loader implementation */ impl = loader_get_impl(py_loader_tag); + /* TODO: Remove this check when we implement this: https://github.com/metacall/core/issues/231 */ if (impl == NULL) { PyErr_SetString(PyExc_ValueError, "Invalid Python loader instance, MetaCall Port must be used from MetaCall CLI"); @@ -778,6 +780,184 @@ static PyObject *py_loader_port_inspect(PyObject *self, PyObject *args) return result; } +static PyObject *py_loader_port_value_create_ptr(PyObject *self, PyObject *args) +{ + static const char format[] = "O:metacall_value_create_ptr"; + PyObject *pointer; + + (void)self; + + /* Parse arguments */ + if (!PyArg_ParseTuple(args, (char *)format, &pointer)) + { + PyErr_SetString(PyExc_TypeError, "Invalid number of arguments, use it like: metacall_value_create_ptr(None); or metacall_value_create_ptr(previous_allocated_ptr);"); + return py_loader_port_none(); + } + + if (!PyCapsule_CheckExact(pointer) && pointer != Py_None) + { + PyErr_SetString(PyExc_TypeError, "Invalid parameter type in first argument must be None or a PyCapsule (i.e a previously allocated pointer)"); + return py_loader_port_none(); + } + + if (pointer == Py_None) + { + return py_loader_impl_capsule_new_null(); + } + else + { + /* Get capsule pointer */ + const char *name = PyCapsule_GetName(pointer); + void *pointer_addr = PyCapsule_GetPointer(pointer, name); + + /* Return a copy of the capsule */ + return PyCapsule_New(pointer_addr, name, NULL); + } +} + +static const char py_loader_capsule_reference_id[] = "__metacall_capsule_reference__"; + +static void py_loader_port_value_reference_destroy(PyObject *capsule) +{ + void *ref = PyCapsule_GetPointer(capsule, py_loader_capsule_reference_id); + void *v = PyCapsule_GetContext(capsule); + + metacall_value_destroy(ref); + metacall_value_destroy(v); +} + +static PyObject *py_loader_port_value_reference(PyObject *self, PyObject *args) +{ + static const char format[] = "O:metacall_value_reference"; + PyObject *obj; + loader_impl impl; + void *v, *ref; + PyObject *capsule; + + (void)self; + + /* Parse arguments */ + if (!PyArg_ParseTuple(args, (char *)format, &obj)) + { + PyErr_SetString(PyExc_TypeError, "Invalid number of arguments, use it like: metacall_value_reference(obj);"); + goto error_none; + } + + /* Obtain Python loader implementation */ + impl = loader_get_impl(py_loader_tag); + + /* TODO: When using the port outside MetaCall this is going to segfault for functions and similar + * structures that require py loader internal structure to be initialized. For those cases, we + * must implement this: https://github.com/metacall/core/issues/231 + */ + v = py_loader_impl_capi_to_value(impl, obj, py_loader_impl_capi_to_value_type(impl, obj)); + + if (v == NULL) + { + PyErr_SetString(PyExc_ValueError, "Failed to convert the Python object to MetaCall value."); + goto error_none; + } + + ref = metacall_value_reference(v); + + if (ref == NULL) + { + PyErr_SetString(PyExc_ValueError, "Failed to create the reference from MetaCall value."); + goto error_value; + } + + capsule = PyCapsule_New(ref, py_loader_capsule_reference_id, &py_loader_port_value_reference_destroy); + + if (capsule == NULL) + { + goto error_ref; + } + + if (PyCapsule_SetContext(capsule, v) != 0) + { + goto error_ref; + } + + return capsule; + +error_ref: + metacall_value_destroy(ref); +error_value: + metacall_value_destroy(v); +error_none: + return py_loader_port_none(); +} + +static PyObject *py_loader_port_value_dereference(PyObject *self, PyObject *args) +{ + static const char format[] = "O:metacall_value_dereference"; + PyObject *capsule; + const char *name = NULL; + void *ref, *v; + loader_impl impl; + PyObject *result; + + (void)self; + + /* Parse arguments */ + if (!PyArg_ParseTuple(args, (char *)format, &capsule)) + { + PyErr_SetString(PyExc_TypeError, "Invalid number of arguments, use it like: metacall_value_dereference(ptr);"); + return py_loader_port_none(); + } + + /* Check if it is a valid reference */ + if (!PyCapsule_CheckExact(capsule)) + { + PyErr_SetString(PyExc_TypeError, "Invalid parameter type in first argument must be a PyCapsule (i.e a previously allocated pointer)"); + return py_loader_port_none(); + } + + /* Check if it is a valid MetaCall reference */ + name = PyCapsule_GetName(capsule); + + if (name != py_loader_capsule_reference_id) + { + PyErr_SetString(PyExc_TypeError, "Invalid reference, argument must be a PyCapsule from MetaCall"); + return py_loader_port_none(); + } + + /* Get the reference */ + ref = PyCapsule_GetPointer(capsule, name); + + if (ref == NULL) + { + return py_loader_port_none(); + } + + /* Get the value */ + v = metacall_value_dereference(ref); + + /* Validate the result */ + if (v != PyCapsule_GetContext(capsule)) + { + PyErr_SetString(PyExc_TypeError, "Invalid reference, the PyCapsule context does not match the dereferenced value"); + return py_loader_port_none(); + } + + /* Obtain Python loader implementation */ + impl = loader_get_impl(py_loader_tag); + + /* TODO: When using the port outside MetaCall this is going to segfault for functions and similar + * structures that require py loader internal structure to be initialized. For those cases, we + * must implement this: https://github.com/metacall/core/issues/231 + */ + result = py_loader_impl_value_to_capi(impl, value_type_id(v), v); + + if (result == NULL) + { + PyErr_SetString(PyExc_ValueError, "Failed to convert the MetaCall value to Python object."); + return py_loader_port_none(); + } + + return result; +} + static PyMethodDef metacall_methods[] = { { "metacall_load_from_file", py_loader_port_load_from_file, METH_VARARGS, "Loads a script from file." }, @@ -793,6 +973,12 @@ static PyMethodDef metacall_methods[] = { "Get information about all loaded objects." }, { "metacall", py_loader_port_invoke, METH_VARARGS, "Call a function anonymously." }, + { "metacall_value_create_ptr", py_loader_port_value_create_ptr, METH_VARARGS, + "Create a new value of type Pointer." }, + { "metacall_value_reference", py_loader_port_value_reference, METH_VARARGS, + "Create a new value of type Pointer." }, + { "metacall_value_dereference", py_loader_port_value_dereference, METH_VARARGS, + "Get the data which a value of type Pointer is pointing to." }, { NULL, NULL, 0, NULL } }; diff --git a/source/metacall/include/metacall/metacall_value.h b/source/metacall/include/metacall/metacall_value.h index 0634e91bf..932cc75f0 100644 --- a/source/metacall/include/metacall/metacall_value.h +++ b/source/metacall/include/metacall/metacall_value.h @@ -394,6 +394,48 @@ METACALL_API const char *metacall_value_type_name(void *v); */ METACALL_API void *metacall_value_copy(void *v); +/** +* @brief +* Creates a new pointer value, with a reference to the +* data contained inside the value @v. For example: +* +* void *v = metacall_value_create_int(20); +* void *ptr = metacall_value_reference(v); +* +* In this case, void *ptr is a value equivalent to int*, +* and it points directly to the integer contained in void *v. +* Note that if we destroy the value @v, the reference will +* point to already freed memory, causing use-after-free when used. +* +* @param[in] v +* Reference to the value to be referenced +* +* @return +* A new value of type pointer, pointing to the @v data +*/ +METACALL_API void *metacall_value_reference(void *v); + +/** +* @brief +* If you pass a reference previously created (i.e a value of +* type pointer, pointing to another value), it returns the +* original value. It does not modify the memory of the values +* neither allocates anything. If the value @v is pointing to +* has been deleted, it will cause an use-after-free. For example: +* +* void *v = metacall_value_create_int(20); +* void *ptr = metacall_value_reference(v); +* void *w = metacall_value_dereference(ptr); +* assert(v == w); // Both are the same value +* +* @param[in] v +* Reference to the value to be dereferenced +* +* @return +* The value containing the data which ptr is pointing to +*/ +METACALL_API void *metacall_value_dereference(void *v); + /** * @brief * Copies the ownership from @src to @dst, including the finalizer, diff --git a/source/metacall/source/metacall_value.c b/source/metacall/source/metacall_value.c index 30222f3ed..b598e599b 100644 --- a/source/metacall/source/metacall_value.c +++ b/source/metacall/source/metacall_value.c @@ -223,6 +223,16 @@ void *metacall_value_copy(void *v) return value_type_copy(v); } +void *metacall_value_reference(void *v) +{ + return value_type_reference(v); +} + +void *metacall_value_dereference(void *v) +{ + return value_type_dereference(v); +} + void metacall_value_move(void *src, void *dst) { value_move(src, dst); diff --git a/source/ports/py_port/metacall/__init__.py b/source/ports/py_port/metacall/__init__.py index 230c1ae7c..ef8a6fd07 100644 --- a/source/ports/py_port/metacall/__init__.py +++ b/source/ports/py_port/metacall/__init__.py @@ -17,4 +17,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -from metacall.api import metacall, metacall_load_from_file, metacall_load_from_memory, metacall_load_from_package, metacall_inspect +from metacall.api import metacall, metacall_load_from_file, metacall_load_from_memory, metacall_load_from_package, metacall_inspect, metacall_value_create_ptr, metacall_value_reference, metacall_value_dereference diff --git a/source/ports/py_port/metacall/api.py b/source/ports/py_port/metacall/api.py index 9ffcea830..6bfb290cc 100644 --- a/source/ports/py_port/metacall/api.py +++ b/source/ports/py_port/metacall/api.py @@ -75,6 +75,18 @@ def metacall_inspect(): return dic return dict() +# Value API for handling pointers +def metacall_value_create_ptr(ptr): + return module.metacall_value_create_ptr(ptr) + +# Value API for getting the pointer to a value +def metacall_value_reference(v): + return module.metacall_value_reference(v) + +# Value API for getting the value of a pointer +def metacall_value_dereference(ptr): + return module.metacall_value_dereference(ptr) + # Monkey patching import builtins import types @@ -157,6 +169,9 @@ def generate_module(handle_name, handle): 'tsx': 'ts', # Rust Loader 'rs': 'rs', + # C Loader + 'c': 'c' + # Note: By default js extension uses NodeJS loader instead of JavaScript V8 # Probably in the future we can differenciate between them, but it is not trivial } diff --git a/source/reflect/include/reflect/reflect_value.h b/source/reflect/include/reflect/reflect_value.h index 208f09993..8b68387bb 100644 --- a/source/reflect/include/reflect/reflect_value.h +++ b/source/reflect/include/reflect/reflect_value.h @@ -169,6 +169,18 @@ REFLECT_API void value_finalizer(value v, value_finalizer_cb finalizer, void *fi */ REFLECT_API void *value_data(value v); +/** +* @brief +* From the data get the value pointer (inverse of value_data function) +* +* @param[in] data +* Reference to the data of a value +* +* @return +* Pointer to the container of the data @data +*/ +REFLECT_API value value_container(void *data); + /** * @brief * Convert value @v to memory block @data diff --git a/source/reflect/include/reflect/reflect_value_type.h b/source/reflect/include/reflect/reflect_value_type.h index 1a84aef99..fdb7856b7 100644 --- a/source/reflect/include/reflect/reflect_value_type.h +++ b/source/reflect/include/reflect/reflect_value_type.h @@ -71,6 +71,48 @@ REFLECT_API value value_type_create(const void *data, size_t bytes, type_id id); */ REFLECT_API value value_type_copy(value v); +/** +* @brief +* Creates a new pointer value, with a reference to the +* data contained inside the value @v. For example: +* +* value v = value_create_int(20); +* value ptr = value_type_reference(v); +* +* In this case, ptr is a value equivalent to int*, +* and it points directly to the integer contained in value v. +* Note that if we destroy the value @v, the reference will +* point to already freed memory, causing use-after-free when used. +* +* @param[in] v +* Reference to the value to be referenced +* +* @return +* A new value of type pointer, pointing to the @v data +*/ +REFLECT_API value value_type_reference(value v); + +/** +* @brief +* If you pass a reference previously created (i.e a value of +* type pointer, pointing to another value), it returns the +* original value. It does not modify the memory of the values +* neither allocates anything. If the value @v is pointing to +* has been deleted, it will cause an use-after-free. For example: +* +* value v = value_create_int(20); +* value ptr = value_type_reference(v); +* value w = value_type_dereference(ptr); +* assert(v == w); // Both are the same value +* +* @param[in] v +* Reference to the value to be dereferenced +* +* @return +* The value containing the data which ptr is pointing to +*/ +REFLECT_API value value_type_dereference(value v); + /** * @brief * Returns the size of the value type diff --git a/source/reflect/source/reflect_value.c b/source/reflect/source/reflect_value.c index 6d69506dc..8fa353752 100644 --- a/source/reflect/source/reflect_value.c +++ b/source/reflect/source/reflect_value.c @@ -200,9 +200,27 @@ void *value_data(value v) return NULL; } + /* Right now the memory layout is designed in a way that + * the first byte of the value is the data itself, so returning + * the value as (void *) has the same effect as accessing the data + */ return v; } +value value_container(void *data) +{ + if (data == NULL) + { + return NULL; + } + + /* Right now the memory layout is designed in a way that + * the first byte of the value is the data itself, so returning + * the data as (value) has the same effect as container_of(data, struct value, data) + */ + return (value)data; +} + void value_to(value v, void *data, size_t bytes) { void *src = value_data(v); diff --git a/source/reflect/source/reflect_value_type.c b/source/reflect/source/reflect_value_type.c index 537e2a805..dcae4ce6f 100644 --- a/source/reflect/source/reflect_value_type.c +++ b/source/reflect/source/reflect_value_type.c @@ -153,6 +153,20 @@ value value_type_copy(value v) return NULL; } +value value_type_reference(value v) +{ + void *data = value_data(v); + + return value_create_ptr(data); +} + +value value_type_dereference(value v) +{ + void *data = value_to_ptr(v); + + return value_container(data); +} + size_t value_type_size(value v) { size_t size = value_size(v); diff --git a/source/scripts/c/loadtest/source/loadtest.cpp b/source/scripts/c/loadtest/source/loadtest.cpp index 537175040..aa750670c 100644 --- a/source/scripts/c/loadtest/source/loadtest.cpp +++ b/source/scripts/c/loadtest/source/loadtest.cpp @@ -37,3 +37,8 @@ void pair_list_destroy(pair_list *t) delete[] t->pairs; delete t; } + +void modify_int_ptr(int *i) +{ + *i = 111; +} diff --git a/source/scripts/c/loadtest/source/loadtest.h b/source/scripts/c/loadtest/source/loadtest.h index 85b8b28b7..cb0552f23 100644 --- a/source/scripts/c/loadtest/source/loadtest.h +++ b/source/scripts/c/loadtest/source/loadtest.h @@ -33,6 +33,8 @@ EXPORT double pair_list_value(pair_list *t, uint32_t id); EXPORT void pair_list_destroy(pair_list *t); +EXPORT void modify_int_ptr(int *i); + #ifdef __cplusplus } #endif diff --git a/source/scripts/python/pointer/source/pointer.py.in b/source/scripts/python/pointer/source/pointer.py.in index 8c0d10d00..bf44b93cf 100644 --- a/source/scripts/python/pointer/source/pointer.py.in +++ b/source/scripts/python/pointer/source/pointer.py.in @@ -7,20 +7,35 @@ try: from metacall import metacall except ImportError as e: print('Error when loading MetaCall Python Port: ' + str(e)) + sys.stdout.flush() def python_set_value(t, value): print('Python python_set_value: ', type(t), t, value) + sys.stdout.flush() result = metacall('native_set_value', t, value) print('Python result from host native_set_value: ' + str(result), type(result)) + sys.stdout.flush() arr = metacall('native_get_value', t) print('Python result from host native_get_value: ' + str(arr), type(arr)) + sys.stdout.flush() if arr != [10, 50, 70]: print('Error: Invalid array values') return None return result + +def python_ret_null(ptr): + print('Python python_ret_null: ', type(ptr), ptr) + sys.stdout.flush() + + result = metacall('native_ret_null_ptr', ptr) + + print('Python native_ret_null_ptr: ', type(result), result) + sys.stdout.flush() + + return result diff --git a/source/tests/metacall_python_pointer_test/source/metacall_python_pointer_test.cpp b/source/tests/metacall_python_pointer_test/source/metacall_python_pointer_test.cpp index 8883a2d53..863ab1cdd 100644 --- a/source/tests/metacall_python_pointer_test/source/metacall_python_pointer_test.cpp +++ b/source/tests/metacall_python_pointer_test/source/metacall_python_pointer_test.cpp @@ -74,6 +74,18 @@ void *native_get_value(size_t argc, void *args[], void *data) return metacall_value_create_array(array, size); } +void *native_ret_null_ptr(size_t argc, void *args[], void *data) +{ + void *ptr = metacall_value_to_ptr(args[0]); + + EXPECT_EQ((void *)ptr, (void *)NULL); + + (void)argc; + (void)data; + + return metacall_value_create_ptr(NULL); +} + TEST_F(metacall_python_pointer_test, DefaultConstructor) { metacall_print_info(); @@ -89,6 +101,10 @@ TEST_F(metacall_python_pointer_test, DefaultConstructor) metacall_register("native_get_value", native_get_value, NULL, METACALL_ARRAY, 1, METACALL_PTR); EXPECT_NE((void *)NULL, (void *)metacall_function("native_get_value")); + + metacall_register("native_ret_null_ptr", native_ret_null_ptr, NULL, METACALL_PTR, 0); + + EXPECT_NE((void *)NULL, (void *)metacall_function("native_ret_null_ptr")); } /* Python */ @@ -126,6 +142,21 @@ TEST_F(metacall_python_pointer_test, DefaultConstructor) EXPECT_EQ((unsigned char)70U, (unsigned char)t.b); metacall_value_destroy(ret); + + void *args[] = { + metacall_value_create_ptr(NULL) + }; + + ret = metacallv("python_ret_null", args); + + EXPECT_NE((void *)NULL, (void *)ret); + + EXPECT_EQ((enum metacall_value_id)METACALL_PTR, (enum metacall_value_id)metacall_value_id(ret)); + + EXPECT_EQ((void *)NULL, (void *)metacall_value_to_ptr(ret)); + + metacall_value_destroy(ret); + metacall_value_destroy(args[0]); } #endif /* OPTION_BUILD_LOADERS_PY */ diff --git a/source/tests/metacall_python_port_pointer_test/CMakeLists.txt b/source/tests/metacall_python_port_pointer_test/CMakeLists.txt new file mode 100644 index 000000000..d421bdbe7 --- /dev/null +++ b/source/tests/metacall_python_port_pointer_test/CMakeLists.txt @@ -0,0 +1,152 @@ +# Check if loaders are enabled +if(NOT OPTION_BUILD_LOADERS OR NOT OPTION_BUILD_LOADERS_PY OR NOT OPTION_BUILD_LOADERS_C OR NOT OPTION_BUILD_PORTS OR NOT OPTION_BUILD_PORTS_PY) + return() +endif() + +# +# Executable name and options +# + +# Target name +set(target metacall-python-port-pointer-test) +message(STATUS "Test ${target}") + +# +# Compiler warnings +# + +include(Warnings) + +# +# Compiler security +# + +include(SecurityFlags) + +# +# Sources +# + +set(include_path "${CMAKE_CURRENT_SOURCE_DIR}/include/${target}") +set(source_path "${CMAKE_CURRENT_SOURCE_DIR}/source") + +set(sources + ${source_path}/main.cpp + ${source_path}/metacall_python_port_pointer_test.cpp +) + +# Group source files +set(header_group "Header Files (API)") +set(source_group "Source Files") +source_group_by_path(${include_path} "\\\\.h$|\\\\.hpp$" + ${header_group} ${headers}) +source_group_by_path(${source_path} "\\\\.cpp$|\\\\.c$|\\\\.h$|\\\\.hpp$" + ${source_group} ${sources}) + +# +# Create executable +# + +# Build executable +add_executable(${target} + ${sources} +) + +# Create namespaced alias +add_executable(${META_PROJECT_NAME}::${target} ALIAS ${target}) + +# +# Project options +# + +set_target_properties(${target} + PROPERTIES + ${DEFAULT_PROJECT_OPTIONS} + FOLDER "${IDE_FOLDER}" +) + +# +# Include directories +# + +target_include_directories(${target} + PRIVATE + ${DEFAULT_INCLUDE_DIRECTORIES} + ${PROJECT_BINARY_DIR}/source/include +) + +# +# Libraries +# + +target_link_libraries(${target} + PRIVATE + ${DEFAULT_LIBRARIES} + + GTest + + ${META_PROJECT_NAME}::metacall +) + +# +# Compile definitions +# + +target_compile_definitions(${target} + PRIVATE + ${DEFAULT_COMPILE_DEFINITIONS} + + # Python Port path + METACALL_PYTHON_PORT_PATH="${CMAKE_SOURCE_DIR}/source/ports/py_port" +) + +# +# Compile options +# + +target_compile_options(${target} + PRIVATE + ${DEFAULT_COMPILE_OPTIONS} +) + +# +# Linker options +# + +target_link_libraries(${target} + PRIVATE + ${DEFAULT_LINKER_OPTIONS} +) + +# +# Define test +# + +add_test(NAME ${target} + COMMAND $ +) + +# +# Define dependencies +# + +add_dependencies(${target} + py_loader + c_loader + loadtest +) + +# +# Define test properties +# + +set_property(TEST ${target} + PROPERTY LABELS ${target} +) + +include(TestEnvironmentVariables) + +test_environment_variables(${target} + "" + ${TESTS_ENVIRONMENT_VARIABLES} +) diff --git a/source/tests/metacall_python_port_pointer_test/source/main.cpp b/source/tests/metacall_python_port_pointer_test/source/main.cpp new file mode 100644 index 000000000..11ddf3f59 --- /dev/null +++ b/source/tests/metacall_python_port_pointer_test/source/main.cpp @@ -0,0 +1,28 @@ +/* + * MetaCall Library by Parra Studios + * A library for providing a foreign function interface calls. + * + * Copyright (C) 2016 - 2024 Vicente Eduardo Ferrer Garcia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +int main(int argc, char *argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/source/tests/metacall_python_port_pointer_test/source/metacall_python_port_pointer_test.cpp b/source/tests/metacall_python_port_pointer_test/source/metacall_python_port_pointer_test.cpp new file mode 100644 index 000000000..48b45d6c5 --- /dev/null +++ b/source/tests/metacall_python_port_pointer_test/source/metacall_python_port_pointer_test.cpp @@ -0,0 +1,109 @@ +/* + * MetaCall Library by Parra Studios + * A library for providing a foreign function interface calls. + * + * Copyright (C) 2016 - 2024 Vicente Eduardo Ferrer Garcia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include +#include +#include + +class metacall_python_port_pointer_test : public testing::Test +{ +public: +}; + +TEST_F(metacall_python_port_pointer_test, DefaultConstructor) +{ + metacall_print_info(); + + ASSERT_EQ((int)0, (int)metacall_initialize()); + + /* Test value reference and dereference */ + void *v = metacall_value_create_int(34551); + + ASSERT_EQ((enum metacall_value_id)METACALL_INT, (enum metacall_value_id)metacall_value_id(v)); + + void *ref = metacall_value_reference(v); + + ASSERT_EQ((enum metacall_value_id)METACALL_PTR, (enum metacall_value_id)metacall_value_id(ref)); + + int *int_ptr = (int *)metacall_value_to_ptr(ref); + + *int_ptr += 10; + + void *result = metacall_value_dereference(ref); + + ASSERT_EQ((enum metacall_value_id)METACALL_INT, (enum metacall_value_id)metacall_value_id(result)); + + ASSERT_EQ((int)34561, (int)metacall_value_to_int(result)); + + ASSERT_EQ((void *)v, (void *)result); + + /* Test Python reference and dereference */ + static const char buffer[] = + "import sys\n" + "sys.path.insert(0, '" METACALL_PYTHON_PORT_PATH "')\n" + "from metacall import metacall_load_from_package, metacall, metacall_value_create_ptr, metacall_value_reference, metacall_value_dereference\n" + "metacall_load_from_package('c', 'loadtest')\n" + + "def test() -> int:\n" + " print('Test start')\n" + " sys.stdout.flush()\n" + + " int_val = 324444\n" + " int_val_ref = metacall_value_reference(int_val)\n" + + " print(int_val_ref)\n" + " sys.stdout.flush()\n" + + " metacall('modify_int_ptr', int_val_ref)\n" + " int_val_deref = metacall_value_dereference(int_val_ref)\n" + + " print(int_val, '!=', int_val_deref)\n" + " sys.stdout.flush()\n" + + " return int_val_deref\n"; + + // "def test() -> int:\n" + // " print('Test start')\n" + // " sys.stdout.flush()\n" + // " list_pair = metacall_value_create_ptr(None)\n" + // " list_pair_ref = metacall_value_reference(list_pair)\n" + // " result = metacall('pair_list_init', list_pair_ref)\n" + // " print(result)\n" + // " sys.stdout.flush()\n" + // " list_pair = metacall_value_dereference(list_pair_ref)\n" + // " result = metacall('pair_list_value', list_pair)\n" + // " print(result)\n" + // " sys.stdout.flush()\n" + // " return result\n"; + + ASSERT_EQ((int)0, (int)metacall_load_from_memory("py", buffer, sizeof(buffer), NULL)); + + void *ret = metacall("test"); + + ASSERT_EQ((enum metacall_value_id)METACALL_LONG, (enum metacall_value_id)metacall_value_id(ret)); + + EXPECT_EQ((long)111L, (long)metacall_value_to_long(ret)); + + metacall_value_destroy(ret); + + EXPECT_EQ((int)0, (int)metacall_destroy()); +}