All of lore.kernel.org
 help / color / mirror / Atom feed
From: Alberto Faria <afaria@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Marc-André Lureau" <marcandre.lureau@redhat.com>,
	"Stefano Garzarella" <sgarzare@redhat.com>,
	"Hannes Reinecke" <hare@suse.com>,
	"Dr. David Alan Gilbert" <dgilbert@redhat.com>,
	"Vladimir Sementsov-Ogievskiy" <vsementsov@yandex-team.ru>,
	"Maciej S. Szmigiero" <maciej.szmigiero@oracle.com>,
	"Peter Lieven" <pl@kamp.de>,
	kvm@vger.kernel.org, "Xie Yongji" <xieyongji@bytedance.com>,
	"Eric Auger" <eric.auger@redhat.com>,
	"Hanna Reitz" <hreitz@redhat.com>,
	"Jeff Cody" <codyprime@gmail.com>,
	"Eric Blake" <eblake@redhat.com>,
	"Denis V. Lunev" <den@openvz.org>,
	"Daniel P. Berrangé" <berrange@redhat.com>,
	"Philippe Mathieu-Daudé" <f4bug@amsat.org>,
	"Christian Schoenebeck" <qemu_oss@crudebyte.com>,
	"Stefan Weil" <sw@weilnetz.de>,
	"Klaus Jensen" <its@irrelevant.dk>,
	"Laurent Vivier" <lvivier@redhat.com>,
	"Alberto Garcia" <berto@igalia.com>,
	"Michael Roth" <michael.roth@amd.com>,
	"Juan Quintela" <quintela@redhat.com>,
	"David Hildenbrand" <david@redhat.com>,
	qemu-block@nongnu.org, "Konstantin Kostiuk" <kkostiuk@redhat.com>,
	"Kevin Wolf" <kwolf@redhat.com>,
	"Gerd Hoffmann" <kraxel@redhat.com>,
	"Stefan Hajnoczi" <stefanha@redhat.com>,
	"Marcelo Tosatti" <mtosatti@redhat.com>,
	"Greg Kurz" <groug@kaod.org>,
	"Michael S. Tsirkin" <mst@redhat.com>,
	"Amit Shah" <amit@kernel.org>,
	"Paolo Bonzini" <pbonzini@redhat.com>,
	"Alex Williamson" <alex.williamson@redhat.com>,
	"Peter Xu" <peterx@redhat.com>,
	"Raphael Norwitz" <raphael.norwitz@nutanix.com>,
	"Ronnie Sahlberg" <ronniesahlberg@gmail.com>,
	"Jason Wang" <jasowang@redhat.com>,
	"Emanuele Giuseppe Esposito" <eesposit@redhat.com>,
	"Richard Henderson" <richard.henderson@linaro.org>,
	"Marcel Apfelbaum" <marcel.apfelbaum@gmail.com>,
	"Dmitry Fleytman" <dmitry.fleytman@gmail.com>,
	"Eduardo Habkost" <eduardo@habkost.net>,
	"Fam Zheng" <fam@euphon.net>, "Thomas Huth" <thuth@redhat.com>,
	"Keith Busch" <kbusch@kernel.org>,
	"Alex Bennée" <alex.bennee@linaro.org>,
	"Richard W.M. Jones" <rjones@redhat.com>,
	"John Snow" <jsnow@redhat.com>,
	"Markus Armbruster" <armbru@redhat.com>,
	"Alberto Faria" <afaria@redhat.com>
Subject: [RFC v2 05/10] static-analyzer: Enforce coroutine_fn restrictions for direct calls
Date: Fri, 29 Jul 2022 14:00:34 +0100	[thread overview]
Message-ID: <20220729130040.1428779-6-afaria@redhat.com> (raw)
In-Reply-To: <20220729130040.1428779-1-afaria@redhat.com>

Add a "coroutine_fn" check to static-analyzer.py that ensures that
non-coroutine_fn functions don't perform direct calls to coroutine_fn
functions.

For the few cases where this must happen, introduce an
__allow_coroutine_fn_call() macro that wraps offending calls and
overrides the static analyzer.

Signed-off-by: Alberto Faria <afaria@redhat.com>
---
 include/qemu/coroutine.h        |  13 +++
 static_analyzer/__init__.py     |  46 ++++++++-
 static_analyzer/coroutine_fn.py | 173 ++++++++++++++++++++++++++++++++
 3 files changed, 231 insertions(+), 1 deletion(-)
 create mode 100644 static_analyzer/coroutine_fn.py

diff --git a/include/qemu/coroutine.h b/include/qemu/coroutine.h
index 08c5bb3c76..40a4037525 100644
--- a/include/qemu/coroutine.h
+++ b/include/qemu/coroutine.h
@@ -42,7 +42,20 @@
  *       ....
  *   }
  */
+#ifdef __clang__
+#define coroutine_fn __attribute__((__annotate__("coroutine_fn")))
+#else
 #define 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.
+ *
+ * You don't want to use this.
+ */
+#define __allow_coroutine_fn_call(call) \
+    ((void)"__allow_coroutine_fn_call", call)
 
 typedef struct Coroutine Coroutine;
 
diff --git a/static_analyzer/__init__.py b/static_analyzer/__init__.py
index 36028724b1..5abdbd21a3 100644
--- a/static_analyzer/__init__.py
+++ b/static_analyzer/__init__.py
@@ -23,8 +23,9 @@
 from clang.cindex import (  # type: ignore
     Cursor,
     CursorKind,
-    TranslationUnit,
     SourceLocation,
+    TranslationUnit,
+    TypeKind,
     conf,
 )
 
@@ -146,6 +147,49 @@ def matcher(n: Cursor) -> bool:
     return any(map(matcher, node.get_children()))
 
 
+def is_annotated_with(node: Cursor, annotation: str) -> bool:
+    return any(is_annotation(c, annotation) for c in node.get_children())
+
+
+def is_annotation(node: Cursor, annotation: str) -> bool:
+    return node.kind == CursorKind.ANNOTATE_ATTR and node.spelling == annotation
+
+
+def is_comma_wrapper(node: Cursor, literal: str) -> bool:
+    """
+    Check if `node` is a "comma-wrapper" with the given string literal.
+
+    A "comma-wrapper" is the pattern `((void)string_literal, expr)`. The `expr`
+    is said to be "comma-wrapped".
+    """
+
+    # TODO: Do we need to check that the operator is `,`? Is there another
+    # operator that can combine void and an expr?
+
+    if node.kind != CursorKind.BINARY_OPERATOR:
+        return False
+
+    [left, _right] = node.get_children()
+
+    if (
+        left.kind != CursorKind.CSTYLE_CAST_EXPR
+        or left.type.kind != TypeKind.VOID
+    ):
+        return False
+
+    [unexposed_expr] = left.get_children()
+
+    if unexposed_expr.kind != CursorKind.UNEXPOSED_EXPR:
+        return False
+
+    [string_literal] = unexposed_expr.get_children()
+
+    return (
+        string_literal.kind == CursorKind.STRING_LITERAL
+        and string_literal.spelling == f'"{literal}"'
+    )
+
+
 # ---------------------------------------------------------------------------- #
 # Checks
 
diff --git a/static_analyzer/coroutine_fn.py b/static_analyzer/coroutine_fn.py
new file mode 100644
index 0000000000..f70a3167eb
--- /dev/null
+++ b/static_analyzer/coroutine_fn.py
@@ -0,0 +1,173 @@
+# ---------------------------------------------------------------------------- #
+
+from clang.cindex import Cursor, CursorKind, TypeKind  # type: ignore
+
+from static_analyzer import (
+    CheckContext,
+    VisitorResult,
+    check,
+    is_annotated_with,
+    is_annotation,
+    is_comma_wrapper,
+    visit,
+)
+
+# ---------------------------------------------------------------------------- #
+
+
+@check("coroutine_fn")
+def check_coroutine_fn(context: CheckContext) -> None:
+    """Reports violations of coroutine_fn rules."""
+
+    def visitor(node: Cursor) -> VisitorResult:
+
+        validate_annotations(context, node)
+
+        if node.kind == CursorKind.FUNCTION_DECL and node.is_definition():
+            check_direct_calls(context, node)
+            return VisitorResult.CONTINUE
+
+        return VisitorResult.RECURSE
+
+    visit(context.translation_unit.cursor, visitor)
+
+
+def validate_annotations(context: CheckContext, node: Cursor) -> None:
+
+    # validate annotation usage
+
+    if is_annotation(node, "coroutine_fn") and (
+        node.parent is None or not is_valid_coroutine_fn_usage(node.parent)
+    ):
+        context.report(node, "invalid coroutine_fn usage")
+
+    if is_comma_wrapper(
+        node, "__allow_coroutine_fn_call"
+    ) and not is_valid_allow_coroutine_fn_call_usage(node):
+        context.report(node, "invalid __allow_coroutine_fn_call usage")
+
+    # reject re-declarations with inconsistent annotations
+
+    if node.kind == CursorKind.FUNCTION_DECL and is_coroutine_fn(
+        node
+    ) != is_coroutine_fn(node.canonical):
+        context.report(
+            node,
+            f"coroutine_fn annotation disagreement with"
+            f" {context.format_location(node.canonical)}",
+        )
+
+
+def check_direct_calls(context: CheckContext, caller: Cursor) -> None:
+    """
+    Reject calls from non-coroutine_fn to coroutine_fn.
+
+    Assumes that `caller` is a function definition.
+    """
+
+    if not is_coroutine_fn(caller):
+
+        def visitor(node: Cursor) -> VisitorResult:
+
+            # We can get "calls" that are actually things like top-level macro
+            # invocations for which `node.referenced` is None.
+
+            if (
+                node.kind == CursorKind.CALL_EXPR
+                and node.referenced is not None
+                and is_coroutine_fn(node.referenced.canonical)
+                and not is_comma_wrapper(
+                    node.parent, "__allow_coroutine_fn_call"
+                )
+            ):
+                context.report(
+                    node,
+                    f"non-coroutine_fn function calls coroutine_fn"
+                    f" {node.referenced.spelling}()",
+                )
+
+            return VisitorResult.RECURSE
+
+        visit(caller, visitor)
+
+
+# ---------------------------------------------------------------------------- #
+
+
+def is_valid_coroutine_fn_usage(parent: Cursor) -> bool:
+    """
+    Check if an occurrence of `coroutine_fn` represented by a node with parent
+    `parent` appears at a valid point in the AST. This is the case if `parent`
+    is:
+
+      - A function declaration/definition, OR
+      - A field/variable/parameter declaration with a function pointer type, OR
+      - A typedef of a function type or function pointer type.
+    """
+
+    if parent.kind == CursorKind.FUNCTION_DECL:
+        return True
+
+    canonical_type = parent.type.get_canonical()
+
+    def parent_type_is_function() -> bool:
+        return canonical_type.kind == TypeKind.FUNCTIONPROTO
+
+    def parent_type_is_function_pointer() -> bool:
+        return (
+            canonical_type.kind == TypeKind.POINTER
+            and canonical_type.get_pointee().kind == TypeKind.FUNCTIONPROTO
+        )
+
+    if parent.kind in [
+        CursorKind.FIELD_DECL,
+        CursorKind.VAR_DECL,
+        CursorKind.PARM_DECL,
+    ]:
+        return parent_type_is_function_pointer()
+
+    if parent.kind == CursorKind.TYPEDEF_DECL:
+        return parent_type_is_function() or parent_type_is_function_pointer()
+
+    return False
+
+
+def is_valid_allow_coroutine_fn_call_usage(node: Cursor) -> bool:
+    """
+    Check if an occurrence of `__allow_coroutine_fn_call()` represented by node
+    `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.
+
+    TODO: Ensure that `__allow_coroutine_fn_call()` is in the body of a
+    non-`coroutine_fn` function.
+    """
+
+    [_, call] = node.get_children()
+
+    return call.kind == CursorKind.CALL_EXPR and is_coroutine_fn(
+        call.referenced
+    )
+
+
+def is_coroutine_fn(node: Cursor) -> bool:
+    """
+    Check whether the given `node` should be considered to be `coroutine_fn`.
+
+    This assumes valid usage of `coroutine_fn`.
+    """
+
+    while node.kind in [CursorKind.PAREN_EXPR, CursorKind.UNEXPOSED_EXPR]:
+        children = list(node.get_children())
+        if len(children) == 1:
+            node = children[0]
+        else:
+            break
+
+    return node.kind == CursorKind.FUNCTION_DECL and is_annotated_with(
+        node, "coroutine_fn"
+    )
+
+
+# ---------------------------------------------------------------------------- #
-- 
2.37.1


  parent reply	other threads:[~2022-07-29 13:02 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-07-29 13:00 [RFC v2 00/10] Introduce an extensible static analyzer Alberto Faria
2022-07-29 13:00 ` [RFC v2 01/10] Add " Alberto Faria
2022-07-29 13:00 ` [RFC v2 02/10] Drop unused static function return values Alberto Faria
2022-08-03 10:46   ` Dr. David Alan Gilbert
2022-08-03 11:07     ` Alberto Faria
2022-08-03 11:15       ` Richard W.M. Jones
2022-08-03 11:37         ` Daniel P. Berrangé
2022-08-03 12:25           ` Peter Maydell
2022-08-03 12:47             ` Richard W.M. Jones
2022-08-12 16:29         ` Alberto Faria
2022-08-03 11:12     ` Daniel P. Berrangé
2022-08-03 12:30   ` Peter Maydell
2022-08-12 16:01     ` Alberto Faria
2022-07-29 13:00 ` [RFC v2 03/10] static-analyzer: Support adding tests to checks Alberto Faria
2022-07-29 13:00 ` [RFC v2 04/10] static-analyzer: Avoid reanalyzing unmodified translation units Alberto Faria
2022-07-29 13:00 ` Alberto Faria [this message]
2022-07-29 13:00 ` [RFC v2 06/10] Fix some direct calls from non-coroutine_fn to coroutine_fn Alberto Faria
2022-07-29 13:00 ` [RFC v2 07/10] static-analyzer: Enforce coroutine_fn restrictions on function pointers Alberto Faria
2022-07-29 13:00 ` [RFC v2 08/10] Fix some bad coroutine_fn indirect calls and pointer assignments Alberto Faria
2022-07-29 13:00 ` [RFC v2 09/10] block: Add no_coroutine_fn marker Alberto Faria
2022-07-29 13:00 ` [RFC v2 10/10] Fix some calls from coroutine_fn to no_coroutine_fn Alberto Faria
2022-08-04 11:44 ` [RFC v2 00/10] Introduce an extensible static analyzer Marc-André Lureau
2022-08-12 15:48   ` Alberto Faria
2022-08-16  7:17     ` Marc-André Lureau
2022-10-13 22:00 ` Paolo Bonzini
2022-10-15 13:14   ` Christian Schoenebeck

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220729130040.1428779-6-afaria@redhat.com \
    --to=afaria@redhat.com \
    --cc=alex.bennee@linaro.org \
    --cc=alex.williamson@redhat.com \
    --cc=amit@kernel.org \
    --cc=armbru@redhat.com \
    --cc=berrange@redhat.com \
    --cc=berto@igalia.com \
    --cc=codyprime@gmail.com \
    --cc=david@redhat.com \
    --cc=den@openvz.org \
    --cc=dgilbert@redhat.com \
    --cc=dmitry.fleytman@gmail.com \
    --cc=eblake@redhat.com \
    --cc=eduardo@habkost.net \
    --cc=eesposit@redhat.com \
    --cc=eric.auger@redhat.com \
    --cc=f4bug@amsat.org \
    --cc=fam@euphon.net \
    --cc=groug@kaod.org \
    --cc=hare@suse.com \
    --cc=hreitz@redhat.com \
    --cc=its@irrelevant.dk \
    --cc=jasowang@redhat.com \
    --cc=jsnow@redhat.com \
    --cc=kbusch@kernel.org \
    --cc=kkostiuk@redhat.com \
    --cc=kraxel@redhat.com \
    --cc=kvm@vger.kernel.org \
    --cc=kwolf@redhat.com \
    --cc=lvivier@redhat.com \
    --cc=maciej.szmigiero@oracle.com \
    --cc=marcandre.lureau@redhat.com \
    --cc=marcel.apfelbaum@gmail.com \
    --cc=michael.roth@amd.com \
    --cc=mst@redhat.com \
    --cc=mtosatti@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=peterx@redhat.com \
    --cc=pl@kamp.de \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=qemu_oss@crudebyte.com \
    --cc=quintela@redhat.com \
    --cc=raphael.norwitz@nutanix.com \
    --cc=richard.henderson@linaro.org \
    --cc=rjones@redhat.com \
    --cc=ronniesahlberg@gmail.com \
    --cc=sgarzare@redhat.com \
    --cc=stefanha@redhat.com \
    --cc=sw@weilnetz.de \
    --cc=thuth@redhat.com \
    --cc=vsementsov@yandex-team.ru \
    --cc=xieyongji@bytedance.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.