[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[RFC 5/8] static-analyzer: Enforce coroutine_fn restrictions on function
From: |
Alberto Faria |
Subject: |
[RFC 5/8] static-analyzer: Enforce coroutine_fn restrictions on function pointers |
Date: |
Sat, 2 Jul 2022 12:33:28 +0100 |
Extend static-analyzer.py to enforce coroutine_fn restrictions on
function pointer operations.
Invalid operations include assigning a coroutine_fn value to a
non-coroutine_fn function pointer, and invoking a coroutine_fn function
pointer from a non-coroutine_fn function.
Signed-off-by: Alberto Faria <afaria@redhat.com>
---
static-analyzer.py | 147 +++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 143 insertions(+), 4 deletions(-)
diff --git a/static-analyzer.py b/static-analyzer.py
index 8abc552b82..485d054052 100755
--- a/static-analyzer.py
+++ b/static-analyzer.py
@@ -36,6 +36,8 @@
TranslationUnit,
TranslationUnitLoadError,
TypeKind,
+ SourceRange,
+ TokenGroup,
)
Cursor.__hash__ = lambda self: self.hash # so `Cursor`s can be dict keys
@@ -515,6 +517,90 @@ def check_coroutine_calls(
log(call, "non-coroutine_fn function calls coroutine_fn")
+@check("coroutine-pointers")
+def check_coroutine_pointers(
+ translation_unit: TranslationUnit,
+ translation_unit_path: str,
+ log: Callable[[Cursor, str], None],
+) -> None:
+
+ # What we would really like is to associate annotation attributes with
types
+ # directly, but that doesn't seem possible. Instead, we have to look at the
+ # relevant variable/field/parameter declarations, and follow typedefs.
+
+ # This doesn't check all possible ways of assigning to a coroutine_fn
+ # field/variable/parameter. That would probably be too much work.
+
+ # TODO: Check struct/union/array initialization.
+ # TODO: Check assignment to struct/union/array fields.
+
+ for [*_, node] in all_paths(translation_unit.cursor):
+
+ # check initialization of variables using coroutine_fn values
+
+ if node.kind == CursorKind.VAR_DECL:
+
+ children = [
+ c
+ for c in node.get_children()
+ if c.kind
+ not in [
+ CursorKind.ANNOTATE_ATTR,
+ CursorKind.INIT_LIST_EXPR,
+ CursorKind.TYPE_REF,
+ CursorKind.UNEXPOSED_ATTR,
+ ]
+ ]
+
+ if (
+ len(children) == 1
+ and not is_coroutine_fn(node)
+ and is_coroutine_fn(children[0])
+ ):
+ log(node, "assigning coroutine_fn to non-coroutine_fn")
+
+ # check initialization of fields using coroutine_fn values
+
+ # TODO: This only checks designator initializers.
+
+ if node.kind == CursorKind.INIT_LIST_EXPR:
+
+ for initializer in filter(
+ lambda n: n.kind == CursorKind.UNEXPOSED_EXPR,
+ node.get_children(),
+ ):
+
+ children = list(initializer.get_children())
+
+ if (
+ len(children) == 2
+ and children[0].kind == CursorKind.MEMBER_REF
+ and not is_coroutine_fn(children[0].referenced)
+ and is_coroutine_fn(children[1])
+ ):
+ log(
+ initializer,
+ "assigning coroutine_fn to non-coroutine_fn",
+ )
+
+ # check assignments of coroutine_fn values to variables or fields
+
+ if is_binary_operator(node, "="):
+
+ [left, right] = node.get_children()
+
+ if (
+ left.kind
+ in [
+ CursorKind.DECL_REF_EXPR,
+ CursorKind.MEMBER_REF_EXPR,
+ ]
+ and not is_coroutine_fn(left.referenced)
+ and is_coroutine_fn(right)
+ ):
+ log(node, "assigning coroutine_fn to non-coroutine_fn")
+
+
# ----------------------------------------------------------------------------
#
# Traversal
@@ -555,6 +641,31 @@ def subtrees_matching(
# Node predicates
+def is_binary_operator(node: Cursor, operator: str) -> bool:
+ return (
+ node.kind == CursorKind.BINARY_OPERATOR
+ and get_binary_operator_spelling(node) == operator
+ )
+
+
+def get_binary_operator_spelling(node: Cursor) -> Optional[str]:
+
+ assert node.kind == CursorKind.BINARY_OPERATOR
+
+ [left, right] = node.get_children()
+
+ op_range = SourceRange.from_locations(left.extent.end, right.extent.start)
+
+ tokens = list(TokenGroup.get_tokens(node.translation_unit, op_range))
+ if not tokens:
+ # Can occur when left and right children extents overlap due to
+ # misparsing.
+ return None
+
+ [op_token, *_] = tokens
+ return op_token.spelling
+
+
def is_valid_coroutine_fn_usage(parent: Cursor) -> bool:
"""
Check if an occurrence of `coroutine_fn` represented by a node with parent
@@ -599,7 +710,13 @@ def is_valid_allow_coroutine_fn_call_usage(node: Cursor)
-> bool:
`node` appears at a valid point in the AST. This is the case if its right
operand is a call to:
- - A function declared with the `coroutine_fn` annotation.
+ - A function declared with the `coroutine_fn` annotation, OR
+ - A field/variable/parameter whose declaration has the `coroutine_fn`
+ annotation, and of function pointer type, OR
+ - [TODO] A field/variable/parameter of a typedef function pointer type,
+ and the typedef has the `coroutine_fn` annotation, OR
+ - [TODO] A field/variable/parameter of a pointer to typedef function
type,
+ and the typedef has the `coroutine_fn` annotation.
TODO: Ensure that `__allow_coroutine_fn_call()` is in the body of a
non-`coroutine_fn` function.
@@ -627,9 +744,31 @@ def is_coroutine_fn(node: Cursor) -> bool:
else:
break
- return node.kind == CursorKind.FUNCTION_DECL and is_annotated_with(
- node, "coroutine_fn"
- )
+ if node.kind in [CursorKind.DECL_REF_EXPR, CursorKind.MEMBER_REF_EXPR]:
+ node = node.referenced
+
+ # ---
+
+ if node.kind == CursorKind.FUNCTION_DECL:
+ return is_annotated_with(node, "coroutine_fn")
+
+ if node.kind in [
+ CursorKind.FIELD_DECL,
+ CursorKind.VAR_DECL,
+ CursorKind.PARM_DECL,
+ ]:
+
+ if is_annotated_with(node, "coroutine_fn"):
+ return True
+
+ # TODO: If type is typedef or pointer to typedef, follow typedef.
+
+ return False
+
+ if node.kind == CursorKind.TYPEDEF_DECL:
+ return is_annotated_with(node, "coroutine_fn")
+
+ return False
def is_annotated_with(node: Cursor, annotation: str) -> bool:
--
2.36.1
[RFC 6/8] Fix some coroutine_fn indirect calls and pointer assignments, Alberto Faria, 2022/07/02
[RFC 7/8] block: Add no_coroutine_fn marker, Alberto Faria, 2022/07/02
[RFC 8/8] Avoid calls from coroutine_fn to no_coroutine_fn, Alberto Faria, 2022/07/02
Re: [RFC 0/8] Introduce an extensible static analyzer, Paolo Bonzini, 2022/07/02
Re: [RFC 0/8] Introduce an extensible static analyzer, Daniel P . Berrangé, 2022/07/04