I need to find the signatures of each function pointer calling expression in a ".c" source file, using clang API in Python.
I don't know if clang has a way to tell whether a CALL_EXPR is a function pointer call or a normal function call, so far this is the only workaround I have.
c: Cursor
if c.kind == CursorKind.CALL_EXPR:
if not (c.referenced and c.referenced.kind == CursorKind.FUNCTION_DECL):
# funtion pointer
# ...
Another troublesome problem is that I want to know the function signature that this function pointer represents, but clang doesn't seem to provide a simple interface for getting the type of the return value. But fortunately, it provides an argument list for the function pointer.
# the return type is invalid
result_type: Type = c.result_type
# the argument list is correct
arg_list = str(', ').join([arg.type.get_canonical().spelling for arg in c.get_arguments()])
print(f'{arg_list}')
I don't want to delve into this CALL_EXPR, because the the expression that returns this function pointer can be very complex, can be a simple function pointer variable, or a type cast, or even a ?:
expression.
I just want to know if there's an easy way to get the specific signature of this function pointer.
I need to find the signatures of each function pointer calling expression in a ".c" source file, using clang API in Python.
I don't know if clang has a way to tell whether a CALL_EXPR is a function pointer call or a normal function call, so far this is the only workaround I have.
c: Cursor
if c.kind == CursorKind.CALL_EXPR:
if not (c.referenced and c.referenced.kind == CursorKind.FUNCTION_DECL):
# funtion pointer
# ...
Another troublesome problem is that I want to know the function signature that this function pointer represents, but clang doesn't seem to provide a simple interface for getting the type of the return value. But fortunately, it provides an argument list for the function pointer.
# the return type is invalid
result_type: Type = c.result_type
# the argument list is correct
arg_list = str(', ').join([arg.type.get_canonical().spelling for arg in c.get_arguments()])
print(f'{arg_list}')
I don't want to delve into this CALL_EXPR, because the the expression that returns this function pointer can be very complex, can be a simple function pointer variable, or a type cast, or even a ?:
expression.
I just want to know if there's an easy way to get the specific signature of this function pointer.
Share Improve this question asked Nov 20, 2024 at 13:01 VerySimpleVerySimple 1846 bronze badges1 Answer
Reset to default 0Recognizing indirect function calls
When using the Python
libclang bindings,
given a CALL_EXPR
node, to check whether it is a direct function call:
Get the callee node as the first child of the call node.
Skip implicit casts and parentheses.
Then check if it is a
DECL_REF_EXPR
to aFUNCTION_DECL
. If so, it is direct.
This is similar to what the code in the question does, except that uses
the referenced
attribute of the call node, which (as it happens) does
not work if the function call is direct but the name of the callee is
inside parentheses (which is sometimes done to avoid invocation of a
same-named macro).
Getting the type signature of the callee
To get the type signature of the callee:
Again, get the callee node as the first child of the call node, and skip implicit casts and parentheses.
Get its type as
callee.type
.If it is a
POINTER
type (which happens when a function pointer is not explicitly dereferenced), skip to the pointee.If it is a
FUNCTIONPROTO
, useget_result()
andargument_types()
to get the return and parameter types.Otherwise it should be
FUNCTIONNOPROTO
, which only has a return type.
The code in the question uses c.get_arguments()
, but that retrieves
the argument expressions. Those typically will be converted
(implicitly or explicitly) to the type of the corresponding parameter if
the function has a prototype, but I think it's better to get the type of
the callee directly so as not to rely on the somewhat complicated
mechanism of implicit argument conversion.
The code in the question also tries to use c.result_type
to get the
return type, but that is wrong since result_type
is actually used to
get the return type of a declaration to which a cursor might refer
(this is not obvious; I had to look at the
source code).
To get the type of the call expression node, just use c.type
. I
think it's preferable to get the return type of the callee function
type, but it should be the same as the type of the call node.
Complete example
Here is a Python script that uses the above methods to print some details about call expressions:
#!/usr/bin/env python3
"""Print function pointer signatures."""
import sys
from clang.cindex import Config, CursorKind, Index, TypeKind
def cursor_loc(c):
"""Return the location of `c` as a string."""
return f"{c.location.line}:{c.location.column}"
def cursor_kind_and_loc(c):
"""Return the kind and location of `c` as a string."""
return f"{c.kind} at {cursor_loc(c)}"
def get_first_child(c):
"""Return the first child of `c`, throwing if it does not have one."""
children = c.get_children()
for child in children:
return child
raise RuntimeError(
f"get_first_child: expected {cursor_kind_and_loc(c)} to have a child")
def is_cast_or_paren(c):
"""True if `c` is a cast or parenthesis expression."""
# Implicit casts unfortunately are "unexposed" in Python libclang.
# That is one of its many deficiencies in comparison to the C++ Clang
# API.
return (c.kind == CursorKind.UNEXPOSED_EXPR or
c.kind == CursorKind.PAREN_EXPR)
def skip_casts_and_parens(c):
"""If cursor `c` is a cast or parenthesis expression, return its
sub-expression, recursively skipping any nested casts/parens."""
if is_cast_or_paren(c):
return skip_casts_and_parens(get_first_child(c))
else:
return c
def print_call_details(c):
"""Print details about function calls within the subtree rooted at
cursor `c`."""
if c.kind == CursorKind.CALL_EXPR:
print(f"Call at {cursor_loc(c)}:")
print(f" call node type: {repr(c.type.spelling)}")
calleeExpr = skip_casts_and_parens(get_first_child(c))
if calleeExpr.kind == CursorKind.DECL_REF_EXPR:
calleeDecl = calleeExpr.referenced
# Call is direct if the callee declaration is a function.
if calleeDecl.kind == CursorKind.FUNCTION_DECL:
print(f" direct callee: {repr(calleeDecl.spelling)}")
calleeType = calleeExpr.type
print(f" callee type: {repr(calleeType.spelling)}")
print(f" callee type kind: {calleeType.kind}")
if calleeType.kind == TypeKind.POINTER:
# If the callee expression in an indirect call is not explicitly
# dereferenced, we get a pointer type here that must be skipped.
print(f" skipping fn ptr type")
calleeType = calleeType.get_pointee()
if calleeType.kind == TypeKind.FUNCTIONPROTO:
print(f" return type: {repr(calleeType.get_result().spelling)}")
i = 0
for paramType in calleeType.argument_types():
print(f" parameter {i} type: {repr(paramType.spelling)}")
i += 1
elif calleeType.kind == TypeKind.FUNCTIONNOPROTO:
print(f" return type: {repr(calleeType.get_result().spelling)}")
print(f" no prototype, so no parameter info")
else:
print(f" unexpected callee type kind: {calleeType.kind}")
for child in c.get_children():
print_call_details(child)
def main():
# Load the Clang module. On my Windows system, using Cygwin Python, I
# seem to have to tell it explicitly the name of the DLL (it being on
# the PATH is not enough).
Config.set_library_file("/cygdrive/d/opt/winlibs-mingw64-13.2/bin/libclang.dll");
index = Index.create()
# Parse the C source code. Disable the warning about functions
# without prototypes just so we can exercise that case.
tu = index.parse("test.c", args=["-Wno-deprecated-non-prototype"]);
# Stop if there were syntax errors.
if len(tu.diagnostics) > 0:
for d in tu.diagnostics:
print(d)
sys.exit(2)
# Parse was successful. Inspect the AST.
print_call_details(tu.cursor)
main()
# EOF
When run on the input:
// test.c
// Input for fnptr-sig.
int (*fnptr)(int, float);
int normal_function(int, float);
int (*fnptr_no_proto)();
void f(int a, float b)
{
normal_function(a, b);
(normal_function)(a, b);
(*fnptr)(a, b);
fnptr(a, b);
fnptr_no_proto(a, b);
}
// EOF
it produces the output:
Call at 11:3:
call node type: 'int'
direct callee: 'normal_function'
callee type: 'int (int, float)'
callee type kind: TypeKind.FUNCTIONPROTO
return type: 'int'
parameter 0 type: 'int'
parameter 1 type: 'float'
Call at 12:3:
call node type: 'int'
direct callee: 'normal_function'
callee type: 'int (int, float)'
callee type kind: TypeKind.FUNCTIONPROTO
return type: 'int'
parameter 0 type: 'int'
parameter 1 type: 'float'
Call at 13:3:
call node type: 'int'
callee type: 'int (int, float)'
callee type kind: TypeKind.FUNCTIONPROTO
return type: 'int'
parameter 0 type: 'int'
parameter 1 type: 'float'
Call at 14:3:
call node type: 'int'
callee type: 'int (*)(int, float)'
callee type kind: TypeKind.POINTER
skipping fn ptr type
return type: 'int'
parameter 0 type: 'int'
parameter 1 type: 'float'
Call at 15:3:
call node type: 'int'
callee type: 'int (*)()'
callee type kind: TypeKind.POINTER
skipping fn ptr type
return type: 'int'
no prototype, so no parameter info
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1742355041a4428261.html
评论列表(0条)