@@ -42,7 +42,7 @@
*
* Read more in docs/devel/block-coroutine-wrapper.rst
*/
-#define generated_co_wrapper
+#define generated_co_wrapper no_coroutine_fn
/* block.c */
typedef struct BlockDriver BlockDriver;
@@ -48,6 +48,18 @@
#define coroutine_fn
#endif
+/**
+ * Mark a function that should never be called from a coroutine context
+ *
+ * This typically means that there is an analogous, coroutine_fn function that
+ * should be used instead.
+ */
+#ifdef __clang__
+#define no_coroutine_fn __attribute__((__annotate__("no_coroutine_fn")))
+#else
+#define no_coroutine_fn
+#endif
+
/**
* This can wrap a call to a coroutine_fn from a non-coroutine_fn function and
* suppress the static analyzer's complaints.
@@ -459,6 +459,12 @@ def check_coroutine_annotation_validity(
):
log(node, "invalid coroutine_fn usage")
+ if is_annotation(node, "no_coroutine_fn") and (
+ ancestors[-1] is None
+ or not is_valid_no_coroutine_fn_usage(ancestors[-1])
+ ):
+ log(node, "invalid no_coroutine_fn usage")
+
if is_comma_wrapper(
node, "__allow_coroutine_fn_call"
) and not is_valid_allow_coroutine_fn_call_usage(node):
@@ -478,6 +484,9 @@ def log_annotation_disagreement(annotation: str) -> None:
if is_coroutine_fn(node) != is_coroutine_fn(node.canonical):
log_annotation_disagreement("coroutine_fn")
+ if is_no_coroutine_fn(node) != is_no_coroutine_fn(node.canonical):
+ log_annotation_disagreement("no_coroutine_fn")
+
@check("coroutine-calls")
def check_coroutine_calls(
@@ -516,6 +525,11 @@ def check_coroutine_calls(
):
log(call, "non-coroutine_fn function calls coroutine_fn")
+ # reject calls from coroutine_fn to no_coroutine_fn
+
+ if caller_is_coroutine and is_no_coroutine_fn(callee):
+ log(call, f"coroutine_fn calls no_coroutine_fn function")
+
@check("coroutine-pointers")
def check_coroutine_pointers(
@@ -704,6 +718,16 @@ def parent_type_is_function_pointer() -> bool:
return False
+def is_valid_no_coroutine_fn_usage(parent: Cursor) -> bool:
+ """
+ Checks if an occurrence of `no_coroutine_fn` represented by a node with
+ parent `parent` appears at a valid point in the AST. This is the case if the
+ parent is a function declaration/definition.
+ """
+
+ return parent.kind == CursorKind.FUNCTION_DECL
+
+
def is_valid_allow_coroutine_fn_call_usage(node: Cursor) -> bool:
"""
Check if an occurrence of `__allow_coroutine_fn_call()` represented by node
@@ -771,6 +795,17 @@ def is_coroutine_fn(node: Cursor) -> bool:
return False
+def is_no_coroutine_fn(node: Cursor) -> bool:
+ """
+ Checks whether the given `node` should be considered to be
+ `no_coroutine_fn`.
+
+ This assumes valid usage of `no_coroutine_fn`.
+ """
+
+ return is_annotated_with(node, "no_coroutine_fn")
+
+
def is_annotated_with(node: Cursor, annotation: str) -> bool:
return any(is_annotation(c, annotation) for c in node.get_children())
When applied to a function, it advertises that it should not be called from coroutine_fn functions. Make generated_co_wrapper evaluate to no_coroutine_fn, as coroutine_fn functions should instead directly call the coroutine_fn that backs the generated_co_wrapper. Extend static-analyzer.py's "coroutine-annotation-validity" and "coroutine-calls" checks to enforce no_coroutine_fn rules. Signed-off-by: Alberto Faria <afaria@redhat.com> --- include/block/block-common.h | 2 +- include/qemu/coroutine.h | 12 ++++++++++++ static-analyzer.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-)