Skip to content

Commit 7f51e0a

Browse files
committed
Solve bug in c loader, improved python port add reference and dereference APIs in reflect.
1 parent 6cc27dc commit 7f51e0a

File tree

19 files changed

+712
-19
lines changed

19 files changed

+712
-19
lines changed

source/loaders/c_loader/source/c_loader_impl.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -826,9 +826,9 @@ int c_loader_impl_initialize_types(loader_impl impl)
826826
{ TYPE_FLOAT, "float" },
827827
{ TYPE_DOUBLE, "double" },
828828

829-
{ TYPE_INVALID, "void" }
829+
{ TYPE_NULL, "void" }
830830

831-
/* TODO: Do more types (and the unsigned versions too?) */
831+
/* TODO: Do more types */
832832
};
833833

834834
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
956956
return TYPE_INT;
957957

958958
case CXType_Void:
959-
return TYPE_INVALID;
959+
return TYPE_NULL;
960960

961961
case CXType_Enum: {
962962
CXCursor referenced = clang_isReference(cursor.kind) ? clang_getCursorReferenced(cursor) : cursor;

source/loaders/py_loader/include/py_loader/py_loader_impl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ PY_LOADER_NO_EXPORT PyObject *py_loader_impl_value_to_capi(loader_impl impl, typ
6060

6161
PY_LOADER_NO_EXPORT int py_loader_impl_finalizer_object(loader_impl impl, PyObject *obj, value v);
6262

63+
PY_LOADER_NO_EXPORT PyObject *py_loader_impl_capsule_new_null(void);
64+
6365
#ifdef __cplusplus
6466
}
6567
#endif

source/loaders/py_loader/source/py_loader_impl.c

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,9 @@ static char *py_loader_impl_main_module = NULL;
221221
/* Holds reference to the original PyCFunction.tp_dealloc method */
222222
static void (*py_loader_impl_pycfunction_dealloc)(PyObject *) = NULL;
223223

224+
/* Implements PyCapsules with null value internally */
225+
static const char py_loader_capsule_null_id[] = "__metacall_capsule_null__";
226+
224227
PyObject *py_loader_impl_finalizer_object_impl(PyObject *self, PyObject *Py_UNUSED(args))
225228
{
226229
value v = PyCapsule_GetPointer(self, NULL);
@@ -273,6 +276,14 @@ int py_loader_impl_finalizer_object(loader_impl impl, PyObject *obj, value v)
273276
return 0;
274277
}
275278

279+
PyObject *py_loader_impl_capsule_new_null(void)
280+
{
281+
/* We want to create a new capsule with contents set to NULL, but PyCapsule
282+
* does not allow that, instead we are going to identify our NULL capsule with
283+
* this configuration (setting the capsule to Py_None) */
284+
return PyCapsule_New(Py_None, py_loader_capsule_null_id, NULL);
285+
}
286+
276287
void py_loader_impl_value_invoke_state_finalize(value v, void *data)
277288
{
278289
PyObject *capsule = (PyObject *)data;
@@ -1142,17 +1153,17 @@ value py_loader_impl_capi_to_value(loader_impl impl, PyObject *obj, type_id id)
11421153
}
11431154
else if (id == TYPE_PTR)
11441155
{
1145-
void *ptr = NULL;
1146-
1147-
#if PY_MAJOR_VERSION == 2
1156+
const char *name = PyCapsule_GetName(obj);
1157+
void *ptr = PyCapsule_GetPointer(obj, name);
11481158

1149-
/* TODO */
1150-
1151-
#elif PY_MAJOR_VERSION == 3
1152-
ptr = PyCapsule_GetPointer(obj, NULL);
1153-
1154-
v = value_create_ptr(ptr);
1155-
#endif
1159+
if (ptr == Py_None && name == py_loader_capsule_null_id)
1160+
{
1161+
v = value_create_ptr(NULL);
1162+
}
1163+
else
1164+
{
1165+
v = value_create_ptr(ptr);
1166+
}
11561167
}
11571168
else if (id == TYPE_FUNCTION)
11581169
{
@@ -1440,13 +1451,12 @@ PyObject *py_loader_impl_value_to_capi(loader_impl impl, type_id id, value v)
14401451
{
14411452
void *ptr = value_to_ptr(v);
14421453

1443-
#if PY_MAJOR_VERSION == 2
1444-
1445-
/* TODO */
1454+
if (ptr == NULL)
1455+
{
1456+
return py_loader_impl_capsule_new_null();
1457+
}
14461458

1447-
#elif PY_MAJOR_VERSION == 3
14481459
return PyCapsule_New(ptr, NULL, NULL);
1449-
#endif
14501460
}
14511461
else if (id == TYPE_FUTURE)
14521462
{

source/loaders/py_loader/source/py_loader_port.c

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ static PyObject *py_loader_port_invoke(PyObject *self, PyObject *var_args)
493493
/* Obtain Python loader implementation */
494494
impl = loader_get_impl(py_loader_tag);
495495

496+
/* TODO: Remove this check when we implement this: https://github.com/metacall/core/issues/231 */
496497
if (impl == NULL)
497498
{
498499
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)
622623
/* Obtain Python loader implementation */
623624
impl = loader_get_impl(py_loader_tag);
624625

626+
/* TODO: Remove this check when we implement this: https://github.com/metacall/core/issues/231 */
625627
if (impl == NULL)
626628
{
627629
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)
778780
return result;
779781
}
780782

783+
static PyObject *py_loader_port_value_create_ptr(PyObject *self, PyObject *args)
784+
{
785+
static const char format[] = "O:metacall_value_create_ptr";
786+
PyObject *pointer;
787+
788+
(void)self;
789+
790+
/* Parse arguments */
791+
if (!PyArg_ParseTuple(args, (char *)format, &pointer))
792+
{
793+
PyErr_SetString(PyExc_TypeError, "Invalid number of arguments, use it like: metacall_value_create_ptr(None); or metacall_value_create_ptr(previous_allocated_ptr);");
794+
return py_loader_port_none();
795+
}
796+
797+
if (!PyCapsule_CheckExact(pointer) && pointer != Py_None)
798+
{
799+
PyErr_SetString(PyExc_TypeError, "Invalid parameter type in first argument must be None or a PyCapsule (i.e a previously allocated pointer)");
800+
return py_loader_port_none();
801+
}
802+
803+
if (pointer == Py_None)
804+
{
805+
return py_loader_impl_capsule_new_null();
806+
}
807+
else
808+
{
809+
/* Get capsule pointer */
810+
const char *name = PyCapsule_GetName(pointer);
811+
void *pointer_addr = PyCapsule_GetPointer(pointer, name);
812+
813+
/* Return a copy of the capsule */
814+
return PyCapsule_New(pointer_addr, name, NULL);
815+
}
816+
}
817+
818+
static const char py_loader_capsule_reference_id[] = "__metacall_capsule_reference__";
819+
820+
static void py_loader_port_value_reference_destroy(PyObject *capsule)
821+
{
822+
void *ref = PyCapsule_GetPointer(capsule, py_loader_capsule_reference_id);
823+
void *v = PyCapsule_GetContext(capsule);
824+
825+
metacall_value_destroy(ref);
826+
metacall_value_destroy(v);
827+
}
828+
829+
static PyObject *py_loader_port_value_reference(PyObject *self, PyObject *args)
830+
{
831+
static const char format[] = "O:metacall_value_reference";
832+
PyObject *obj;
833+
loader_impl impl;
834+
void *v, *ref;
835+
PyObject *capsule;
836+
837+
(void)self;
838+
839+
/* Parse arguments */
840+
if (!PyArg_ParseTuple(args, (char *)format, &obj))
841+
{
842+
PyErr_SetString(PyExc_TypeError, "Invalid number of arguments, use it like: metacall_value_reference(obj);");
843+
goto error_none;
844+
}
845+
846+
/* Obtain Python loader implementation */
847+
impl = loader_get_impl(py_loader_tag);
848+
849+
/* TODO: When using the port outside MetaCall this is going to segfault for functions and similar
850+
* structures that require py loader internal structure to be initialized. For those cases, we
851+
* must implement this: https://github.com/metacall/core/issues/231
852+
*/
853+
v = py_loader_impl_capi_to_value(impl, obj, py_loader_impl_capi_to_value_type(impl, obj));
854+
855+
if (v == NULL)
856+
{
857+
PyErr_SetString(PyExc_ValueError, "Failed to convert the Python object to MetaCall value.");
858+
goto error_none;
859+
}
860+
861+
ref = metacall_value_reference(v);
862+
863+
if (ref == NULL)
864+
{
865+
PyErr_SetString(PyExc_ValueError, "Failed to create the reference from MetaCall value.");
866+
goto error_value;
867+
}
868+
869+
capsule = PyCapsule_New(ref, py_loader_capsule_reference_id, &py_loader_port_value_reference_destroy);
870+
871+
if (capsule == NULL)
872+
{
873+
goto error_ref;
874+
}
875+
876+
if (PyCapsule_SetContext(capsule, v) != 0)
877+
{
878+
goto error_ref;
879+
}
880+
881+
return capsule;
882+
883+
error_ref:
884+
metacall_value_destroy(ref);
885+
error_value:
886+
metacall_value_destroy(v);
887+
error_none:
888+
return py_loader_port_none();
889+
}
890+
891+
static PyObject *py_loader_port_value_dereference(PyObject *self, PyObject *args)
892+
{
893+
static const char format[] = "O:metacall_value_dereference";
894+
PyObject *capsule;
895+
const char *name = NULL;
896+
void *ref, *v;
897+
loader_impl impl;
898+
PyObject *result;
899+
900+
(void)self;
901+
902+
/* Parse arguments */
903+
if (!PyArg_ParseTuple(args, (char *)format, &capsule))
904+
{
905+
PyErr_SetString(PyExc_TypeError, "Invalid number of arguments, use it like: metacall_value_dereference(ptr);");
906+
return py_loader_port_none();
907+
}
908+
909+
/* Check if it is a valid reference */
910+
if (!PyCapsule_CheckExact(capsule))
911+
{
912+
PyErr_SetString(PyExc_TypeError, "Invalid parameter type in first argument must be a PyCapsule (i.e a previously allocated pointer)");
913+
return py_loader_port_none();
914+
}
915+
916+
/* Check if it is a valid MetaCall reference */
917+
name = PyCapsule_GetName(capsule);
918+
919+
if (name != py_loader_capsule_reference_id)
920+
{
921+
PyErr_SetString(PyExc_TypeError, "Invalid reference, argument must be a PyCapsule from MetaCall");
922+
return py_loader_port_none();
923+
}
924+
925+
/* Get the reference */
926+
ref = PyCapsule_GetPointer(capsule, name);
927+
928+
if (ref == NULL)
929+
{
930+
return py_loader_port_none();
931+
}
932+
933+
/* Get the value */
934+
v = metacall_value_dereference(ref);
935+
936+
/* Validate the result */
937+
if (v != PyCapsule_GetContext(capsule))
938+
{
939+
PyErr_SetString(PyExc_TypeError, "Invalid reference, the PyCapsule context does not match the dereferenced value");
940+
return py_loader_port_none();
941+
}
942+
943+
/* Obtain Python loader implementation */
944+
impl = loader_get_impl(py_loader_tag);
945+
946+
/* TODO: When using the port outside MetaCall this is going to segfault for functions and similar
947+
* structures that require py loader internal structure to be initialized. For those cases, we
948+
* must implement this: https://github.com/metacall/core/issues/231
949+
*/
950+
result = py_loader_impl_value_to_capi(impl, value_type_id(v), v);
951+
952+
if (result == NULL)
953+
{
954+
PyErr_SetString(PyExc_ValueError, "Failed to convert the MetaCall value to Python object.");
955+
return py_loader_port_none();
956+
}
957+
958+
return result;
959+
}
960+
781961
static PyMethodDef metacall_methods[] = {
782962
{ "metacall_load_from_file", py_loader_port_load_from_file, METH_VARARGS,
783963
"Loads a script from file." },
@@ -793,6 +973,12 @@ static PyMethodDef metacall_methods[] = {
793973
"Get information about all loaded objects." },
794974
{ "metacall", py_loader_port_invoke, METH_VARARGS,
795975
"Call a function anonymously." },
976+
{ "metacall_value_create_ptr", py_loader_port_value_create_ptr, METH_VARARGS,
977+
"Create a new value of type Pointer." },
978+
{ "metacall_value_reference", py_loader_port_value_reference, METH_VARARGS,
979+
"Create a new value of type Pointer." },
980+
{ "metacall_value_dereference", py_loader_port_value_dereference, METH_VARARGS,
981+
"Get the data which a value of type Pointer is pointing to." },
796982
{ NULL, NULL, 0, NULL }
797983
};
798984

source/metacall/include/metacall/metacall_value.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,48 @@ METACALL_API const char *metacall_value_type_name(void *v);
394394
*/
395395
METACALL_API void *metacall_value_copy(void *v);
396396

397+
/**
398+
* @brief
399+
* Creates a new pointer value, with a reference to the
400+
* data contained inside the value @v. For example:
401+
*
402+
* void *v = metacall_value_create_int(20);
403+
* void *ptr = metacall_value_reference(v);
404+
*
405+
* In this case, void *ptr is a value equivalent to int*,
406+
* and it points directly to the integer contained in void *v.
407+
* Note that if we destroy the value @v, the reference will
408+
* point to already freed memory, causing use-after-free when used.
409+
*
410+
* @param[in] v
411+
* Reference to the value to be referenced
412+
*
413+
* @return
414+
* A new value of type pointer, pointing to the @v data
415+
*/
416+
METACALL_API void *metacall_value_reference(void *v);
417+
418+
/**
419+
* @brief
420+
* If you pass a reference previously created (i.e a value of
421+
* type pointer, pointing to another value), it returns the
422+
* original value. It does not modify the memory of the values
423+
* neither allocates anything. If the value @v is pointing to
424+
* has been deleted, it will cause an use-after-free. For example:
425+
*
426+
* void *v = metacall_value_create_int(20);
427+
* void *ptr = metacall_value_reference(v);
428+
* void *w = metacall_value_dereference(ptr);
429+
* assert(v == w); // Both are the same value
430+
*
431+
* @param[in] v
432+
* Reference to the value to be dereferenced
433+
*
434+
* @return
435+
* The value containing the data which ptr is pointing to
436+
*/
437+
METACALL_API void *metacall_value_dereference(void *v);
438+
397439
/**
398440
* @brief
399441
* Copies the ownership from @src to @dst, including the finalizer,

source/metacall/source/metacall_value.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,16 @@ void *metacall_value_copy(void *v)
223223
return value_type_copy(v);
224224
}
225225

226+
void *metacall_value_reference(void *v)
227+
{
228+
return value_type_reference(v);
229+
}
230+
231+
void *metacall_value_dereference(void *v)
232+
{
233+
return value_type_dereference(v);
234+
}
235+
226236
void metacall_value_move(void *src, void *dst)
227237
{
228238
value_move(src, dst);

source/ports/py_port/metacall/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@
1717
# See the License for the specific language governing permissions and
1818
# limitations under the License.
1919

20-
from metacall.api import metacall, metacall_load_from_file, metacall_load_from_memory, metacall_load_from_package, metacall_inspect
20+
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

0 commit comments

Comments
 (0)