Skip to content

Commit 08826fc

Browse files
committed
Corrected segmentation fault in python when an invalid annotation is being used in some function, add two new APIs to introspect a function signature.
1 parent f898dda commit 08826fc

File tree

7 files changed

+348
-7
lines changed

7 files changed

+348
-7
lines changed

source/loaders/py_loader/source/py_loader_impl.c

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,24 +1969,39 @@ int py_loader_impl_clear(loader_impl impl, loader_handle handle)
19691969
return 1;
19701970
}
19711971

1972-
type py_loader_impl_discover_type(loader_impl impl, PyObject *annotation)
1972+
type py_loader_impl_discover_type(loader_impl impl, PyObject *annotation, const char * func_name, const char * parameter_name)
19731973
{
19741974
type t = NULL;
19751975

19761976
if (annotation != NULL)
19771977
{
1978-
PyObject *annotation_qualname = PyObject_GetAttrString(annotation, "__qualname__");
1978+
static const char qualname[] = "__qualname__";
19791979

1980+
if (PyObject_HasAttrString(annotation, qualname) == 0)
1981+
{
1982+
if (parameter_name != NULL)
1983+
{
1984+
log_write("metacall", LOG_LEVEL_WARNING, "Invalid annotation type in the parameter '%s' of the function %s", parameter_name, func_name);
1985+
}
1986+
else
1987+
{
1988+
log_write("metacall", LOG_LEVEL_WARNING, "Invalid annotation type in the return type of the function %s", func_name);
1989+
}
1990+
1991+
return NULL;
1992+
}
1993+
1994+
PyObject *annotation_qualname = PyObject_GetAttrString(annotation, qualname);
19801995
const char *annotation_name = PyUnicode_AsUTF8(annotation_qualname);
19811996

19821997
if (strcmp(annotation_name, "_empty") != 0)
19831998
{
19841999
t = loader_impl_type(impl, annotation_name);
19852000

19862001
log_write("metacall", LOG_LEVEL_DEBUG, "Discover type (%p) (%p): %s", (void *)annotation, (void *)type_derived(t), annotation_name);
1987-
1988-
Py_DECREF(annotation_qualname);
19892002
}
2003+
2004+
Py_DECREF(annotation_qualname);
19902005
}
19912006

19922007
return t;
@@ -2074,6 +2089,8 @@ int py_loader_impl_discover_func(loader_impl impl, PyObject *func, function f)
20742089
{
20752090
signature s = function_signature(f);
20762091

2092+
const char * func_name = function_name(f);
2093+
20772094
PyObject *parameters = PyObject_GetAttrString(result, "parameters");
20782095

20792096
PyObject *return_annotation = PyObject_GetAttrString(result, "return_annotation");
@@ -2111,15 +2128,15 @@ int py_loader_impl_discover_func(loader_impl impl, PyObject *func, function f)
21112128

21122129
PyObject *annotation = PyObject_GetAttrString(parameter, "annotation");
21132130

2114-
type t = py_loader_impl_discover_type(impl, annotation);
2131+
type t = py_loader_impl_discover_type(impl, annotation, func_name, parameter_name);
21152132

21162133
signature_set(s, iterator, parameter_name, t);
21172134
}
21182135
}
21192136
}
21202137
}
21212138

2122-
signature_set_return(s, py_loader_impl_discover_type(impl, return_annotation));
2139+
signature_set_return(s, py_loader_impl_discover_type(impl, return_annotation, func_name, NULL));
21232140

21242141
return 0;
21252142
}

source/metacall/include/metacall/metacall.h

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,41 @@ METACALL_API void * metacallt_s(const char * name, const enum metacall_value_id
371371
* @return
372372
* Function reference, null if the function does not exist
373373
*/
374-
METACALL_API void * metacall_function(const char * name);
374+
METACALL_API void * metacall_function(const char * name);
375+
376+
/**
377+
* @brief
378+
* Get the function parameter type id
379+
*
380+
* @param[in] func
381+
* The pointer to the function obtained from metacall_function
382+
*
383+
* @param[in] parameter
384+
* The index of the parameter to be retrieved
385+
*
386+
* @param[out] id
387+
* The parameter type id that will be returned
388+
*
389+
* @return
390+
* Return 0 if the @parameter index exists and @func is valid, 1 otherwhise
391+
*/
392+
METACALL_API int metacall_function_parameter_type(void * func, size_t parameter, enum metacall_value_id * id);
393+
394+
/**
395+
* @brief
396+
* Get the function return type id
397+
*
398+
* @param[in] func
399+
* The pointer to the function obtained from metacall_function
400+
*
401+
*
402+
* @param[out] id
403+
* The value id of the return type of the function @func
404+
*
405+
* @return
406+
* Return 0 if the @func is valid, 1 otherwhise
407+
*/
408+
METACALL_API int metacall_function_return_type(void * func, enum metacall_value_id * id);
375409

376410
/**
377411
* @brief

source/metacall/source/metacall.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,43 @@ void * metacall_function(const char * name)
610610
}
611611

612612
return f;
613+
}
614+
615+
int metacall_function_parameter_type(void * func, size_t parameter, enum metacall_value_id * id)
616+
{
617+
if (func != NULL)
618+
{
619+
function f = (function)func;
620+
signature s = function_signature(f);
621+
622+
if (parameter < signature_count(s))
623+
{
624+
*id = type_index(signature_get_type(s, parameter));
625+
626+
return 0;
627+
}
628+
}
629+
630+
*id = METACALL_INVALID;
631+
632+
return 1;
633+
}
634+
635+
int metacall_function_return_type(void * func, enum metacall_value_id * id)
636+
{
637+
if (func != NULL)
638+
{
639+
function f = (function)func;
640+
signature s = function_signature(f);
641+
642+
*id = type_index(signature_get_return(s));
643+
644+
return 0;
645+
}
646+
647+
*id = METACALL_INVALID;
648+
649+
return 1;
613650
}
614651

615652
size_t metacall_function_size(void * func)

source/tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ add_subdirectory(metacall_python_varargs_test)
156156
add_subdirectory(metacall_python_port_test)
157157
add_subdirectory(metacall_python_port_https_test)
158158
add_subdirectory(metacall_python_callback_test)
159+
add_subdirectory(metacall_python_fail_test)
159160
add_subdirectory(metacall_map_test)
160161
add_subdirectory(metacall_map_await_test)
161162
add_subdirectory(metacall_initialize_test)
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Check if this loader is enabled
2+
if(NOT OPTION_BUILD_LOADERS OR NOT OPTION_BUILD_LOADERS_PY)
3+
return()
4+
endif()
5+
6+
#
7+
# Executable name and options
8+
#
9+
10+
# Target name
11+
set(target metacall-python-fail-test)
12+
message(STATUS "Test ${target}")
13+
14+
#
15+
# Compiler warnings
16+
#
17+
18+
include(Warnings)
19+
20+
#
21+
# Compiler security
22+
#
23+
24+
include(SecurityFlags)
25+
26+
#
27+
# Sources
28+
#
29+
30+
set(include_path "${CMAKE_CURRENT_SOURCE_DIR}/include/${target}")
31+
set(source_path "${CMAKE_CURRENT_SOURCE_DIR}/source")
32+
33+
set(sources
34+
${source_path}/main.cpp
35+
${source_path}/metacall_python_fail_test.cpp
36+
)
37+
38+
# Group source files
39+
set(header_group "Header Files (API)")
40+
set(source_group "Source Files")
41+
source_group_by_path(${include_path} "\\\\.h$|\\\\.hpp$"
42+
${header_group} ${headers})
43+
source_group_by_path(${source_path} "\\\\.cpp$|\\\\.c$|\\\\.h$|\\\\.hpp$"
44+
${source_group} ${sources})
45+
46+
#
47+
# Create executable
48+
#
49+
50+
# Build executable
51+
add_executable(${target}
52+
${sources}
53+
)
54+
55+
# Create namespaced alias
56+
add_executable(${META_PROJECT_NAME}::${target} ALIAS ${target})
57+
58+
#
59+
# Project options
60+
#
61+
62+
set_target_properties(${target}
63+
PROPERTIES
64+
${DEFAULT_PROJECT_OPTIONS}
65+
FOLDER "${IDE_FOLDER}"
66+
)
67+
68+
#
69+
# Include directories
70+
#
71+
72+
target_include_directories(${target}
73+
PRIVATE
74+
${DEFAULT_INCLUDE_DIRECTORIES}
75+
${PROJECT_BINARY_DIR}/source/include
76+
)
77+
78+
#
79+
# Libraries
80+
#
81+
82+
target_link_libraries(${target}
83+
PRIVATE
84+
${DEFAULT_LIBRARIES}
85+
86+
GTest
87+
88+
${META_PROJECT_NAME}::metacall_distributable
89+
)
90+
91+
#
92+
# Compile definitions
93+
#
94+
95+
target_compile_definitions(${target}
96+
PRIVATE
97+
${DEFAULT_COMPILE_DEFINITIONS}
98+
)
99+
100+
#
101+
# Compile options
102+
#
103+
104+
target_compile_options(${target}
105+
PRIVATE
106+
${DEFAULT_COMPILE_OPTIONS}
107+
)
108+
109+
#
110+
# Linker options
111+
#
112+
113+
target_link_libraries(${target}
114+
PRIVATE
115+
${DEFAULT_LINKER_OPTIONS}
116+
)
117+
118+
#
119+
# Define test
120+
#
121+
122+
add_test(NAME ${target}
123+
COMMAND $<TARGET_FILE:${target}>
124+
)
125+
126+
#
127+
# Define dependencies
128+
#
129+
130+
add_dependencies(${target}
131+
py_loader
132+
)
133+
134+
#
135+
# Define test properties
136+
#
137+
138+
set_property(TEST ${target}
139+
PROPERTY LABELS ${target}
140+
)
141+
142+
include(TestEnvironmentVariables)
143+
144+
test_environment_variables(${target}
145+
""
146+
${TESTS_ENVIRONMENT_VARIABLES}
147+
)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* MetaCall Library by Parra Studios
3+
* A library for providing a foreign function interface calls.
4+
*
5+
* Copyright (C) 2016 - 2021 Vicente Eduardo Ferrer Garcia <vic798@gmail.com>
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
*/
20+
21+
#include <gmock/gmock.h>
22+
23+
int main(int argc, char * argv[])
24+
{
25+
::testing::InitGoogleMock(&argc, argv);
26+
27+
return RUN_ALL_TESTS();
28+
}

0 commit comments

Comments
 (0)