prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Some ansible playbooks cause checkov crash
  Continuation of #4471 
  
  The following playbook (probably because of missing tasks) still causes checkov to crash:
  
  ```yaml
  - name: Sample play
    hosts:
      - test
    roles:
      - role: somerole
  ```
  
  cc @gruebel 
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `checkov/ansible/graph_builder/local_graph.py`
  Content:
  ```
  1 from __future__ import annotations
  2 
  3 import logging
  4 from copy import deepcopy
  5 from pathlib import Path
  6 from typing import Any
  7 
  8 from checkov.common.graph.graph_builder import CustomAttributes
  9 from checkov.common.graph.graph_builder.consts import GraphSource, SELF_REFERENCE
  10 from checkov.common.graph.graph_builder.graph_components.block_types import BlockType
  11 from checkov.common.graph.graph_builder.graph_components.blocks import Block
  12 from checkov.common.runners.graph_builder.local_graph import ObjectLocalGraph
  13 from checkov.common.util.consts import START_LINE, END_LINE
  14 from checkov.ansible.graph_builder.graph_components.resource_types import ResourceType
  15 from checkov.ansible.utils import get_scannable_file_paths, TASK_RESERVED_KEYWORDS, parse_file
  16 
  17 
  18 class AnsibleLocalGraph(ObjectLocalGraph):
  19     def __init__(self, definitions: dict[str | Path, dict[str, Any] | list[dict[str, Any]]]) -> None:
  20         super().__init__(definitions=definitions)
  21 
  22         self.source = GraphSource.ANSIBLE
  23 
  24     def _create_vertices(self) -> None:
  25         for file_path, definition in self.definitions.items():
  26             if not isinstance(definition, list):
  27                 logging.debug(f"definition of file {file_path} has the wrong type {type(definition)}")
  28                 continue
  29 
  30             file_path = str(file_path)
  31 
  32             for code_block in definition:
  33                 if ResourceType.TASKS in code_block:
  34                     for task in code_block[ResourceType.TASKS]:
  35                         self._process_blocks(file_path=file_path, task=task)
  36                 else:
  37                     self._process_blocks(file_path=file_path, task=code_block)
  38 
  39     def _process_blocks(self, file_path: str, task: Any, prefix: str = "") -> None:
  40         """Checks for possible block usage"""
  41 
  42         if not task or not isinstance(task, dict):
  43             return
  44 
  45         if "block" in task and isinstance(task["block"], list):
  46             prefix += f"{ResourceType.BLOCK}."  # with each nested level an extra block prefix is added
  47             self._create_block_vertices(file_path=file_path, block=task, prefix=prefix)
  48 
  49             for block_task in task["block"]:
  50                 self._process_blocks(file_path=file_path, task=block_task, prefix=prefix)
  51         else:
  52             self._create_tasks_vertices(file_path=file_path, task=task, prefix=prefix)
  53 
  54     def _create_tasks_vertices(self, file_path: str, task: Any, prefix: str = "") -> None:
  55         """Creates tasks vertices"""
  56 
  57         if not task or not isinstance(task, dict):
  58             return
  59 
  60         # grab the task name at the beginning before trying to find the actual module name
  61         task_name = task.get("name") or "unknown"
  62 
  63         for name, config in task.items():
  64             if name in TASK_RESERVED_KEYWORDS:
  65                 continue
  66             if name in (START_LINE, END_LINE):
  67                 continue
  68 
  69             resource_type = f"{ResourceType.TASKS}.{name}"
  70 
  71             if isinstance(config, str):
  72                 # this happens when modules have no parameters and are directly used with the user input
  73                 # ex. ansible.builtin.command: cat /etc/passwd
  74                 config = {SELF_REFERENCE: config}
  75             elif config is None:
  76                 # this happens when modules have no parameters and are passed no value
  77                 # ex. amazon.aws.ec2_instance_info:
  78                 config = {
  79                     START_LINE: task[START_LINE],
  80                     END_LINE: task[END_LINE],
  81                 }
  82 
  83             attributes = deepcopy(config)
  84             attributes[CustomAttributes.RESOURCE_TYPE] = resource_type
  85 
  86             # only the module code is relevant for validation,
  87             # but in the check result the whole task should be visible
  88             attributes[START_LINE] = task[START_LINE]
  89             attributes[END_LINE] = task[END_LINE]
  90 
  91             self.vertices.append(
  92                 Block(
  93                     name=f"{resource_type}.{task_name}",
  94                     config=config,
  95                     path=file_path,
  96                     block_type=BlockType.RESOURCE,
  97                     attributes=attributes,
  98                     id=f"{resource_type}.{prefix}{task_name}",
  99                     source=self.source,
  100                 )
  101             )
  102 
  103             # no need to further check
  104             break
  105 
  106     def _create_block_vertices(self, file_path: str, block: dict[str, Any], prefix: str = "") -> None:
  107         """Creates block vertices"""
  108 
  109         # grab the block name, if it exists
  110         block_name = block.get("name") or "unknown"
  111 
  112         config = block
  113         attributes = deepcopy(config)
  114         attributes[CustomAttributes.RESOURCE_TYPE] = ResourceType.BLOCK
  115         del attributes[ResourceType.BLOCK]  # the real block content are tasks, which have their own vertices
  116 
  117         self.vertices.append(
  118             Block(
  119                 name=f"{ResourceType.BLOCK}.{block_name}",
  120                 config=config,
  121                 path=file_path,
  122                 block_type=BlockType.RESOURCE,
  123                 attributes=attributes,
  124                 id=f"{prefix}{block_name}",
  125                 source=self.source,
  126             )
  127         )
  128 
  129     def _create_edges(self) -> None:
  130         return None
  131 
  132     @staticmethod
  133     def get_files_definitions(root_folder: str | Path) -> dict[str | Path, dict[str, Any] | list[dict[str, Any]]]:
  134         definitions: "dict[str | Path, dict[str, Any] | list[dict[str, Any]]]" = {}
  135         file_paths = get_scannable_file_paths(root_folder=root_folder)
  136 
  137         for file_path in file_paths:
  138             result = parse_file(f=file_path)
  139             if result is not None:
  140                 definitions[file_path] = result[0]
  141 
  142         return definitions
  143
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/checkov/ansible/graph_builder/local_graph.py b/checkov/ansible/graph_builder/local_graph.py
  --- a/checkov/ansible/graph_builder/local_graph.py
  +++ b/checkov/ansible/graph_builder/local_graph.py
  @@ -42,11 +42,11 @@
           if not task or not isinstance(task, dict):
               return
   
  -        if "block" in task and isinstance(task["block"], list):
  +        if ResourceType.BLOCK in task and isinstance(task[ResourceType.BLOCK], list):
               prefix += f"{ResourceType.BLOCK}."  # with each nested level an extra block prefix is added
               self._create_block_vertices(file_path=file_path, block=task, prefix=prefix)
   
  -            for block_task in task["block"]:
  +            for block_task in task[ResourceType.BLOCK]:
                   self._process_blocks(file_path=file_path, task=block_task, prefix=prefix)
           else:
               self._create_tasks_vertices(file_path=file_path, task=task, prefix=prefix)
  @@ -65,6 +65,9 @@
                   continue
               if name in (START_LINE, END_LINE):
                   continue
  +            if isinstance(config, list):
  +                # either it is actually not an Ansible file or a playbook without tasks refs
  +                continue
   
               resource_type = f"{ResourceType.TASKS}.{name}"
  
verification_info: {"golden_diff": "diff --git a/checkov/ansible/graph_builder/local_graph.py b/checkov/ansible/graph_builder/local_graph.py\n--- a/checkov/ansible/graph_builder/local_graph.py\n+++ b/checkov/ansible/graph_builder/local_graph.py\n@@ -42,11 +42,11 @@\n         if not task or not isinstance(task, dict):\n             return\n \n-        if \"block\" in task and isinstance(task[\"block\"], list):\n+        if ResourceType.BLOCK in task and isinstance(task[ResourceType.BLOCK], list):\n             prefix += f\"{ResourceType.BLOCK}.\"  # with each nested level an extra block prefix is added\n             self._create_block_vertices(file_path=file_path, block=task, prefix=prefix)\n \n-            for block_task in task[\"block\"]:\n+            for block_task in task[ResourceType.BLOCK]:\n                 self._process_blocks(file_path=file_path, task=block_task, prefix=prefix)\n         else:\n             self._create_tasks_vertices(file_path=file_path, task=task, prefix=prefix)\n@@ -65,6 +65,9 @@\n                 continue\n             if name in (START_LINE, END_LINE):\n                 continue\n+            if isinstance(config, list):\n+                # either it is actually not an Ansible file or a playbook without tasks refs\n+                continue\n \n             resource_type = f\"{ResourceType.TASKS}.{name}\"\n", "issue": "Some ansible playbooks cause checkov crash\nContinuation of #4471 \r\n\r\nThe following playbook (probably because of missing tasks) still causes checkov to crash:\r\n\r\n```yaml\r\n- name: Sample play\r\n  hosts:\r\n    - test\r\n  roles:\r\n    - role: somerole\r\n```\r\n\r\ncc @gruebel \n", "before_files": [{"content": "from __future__ import annotations\n\nimport logging\nfrom copy import deepcopy\nfrom pathlib import Path\nfrom typing import Any\n\nfrom checkov.common.graph.graph_builder import CustomAttributes\nfrom checkov.common.graph.graph_builder.consts import GraphSource, SELF_REFERENCE\nfrom checkov.common.graph.graph_builder.graph_components.block_types import BlockType\nfrom checkov.common.graph.graph_builder.graph_components.blocks import Block\nfrom checkov.common.runners.graph_builder.local_graph import ObjectLocalGraph\nfrom checkov.common.util.consts import START_LINE, END_LINE\nfrom checkov.ansible.graph_builder.graph_components.resource_types import ResourceType\nfrom checkov.ansible.utils import get_scannable_file_paths, TASK_RESERVED_KEYWORDS, parse_file\n\n\nclass AnsibleLocalGraph(ObjectLocalGraph):\n    def __init__(self, definitions: dict[str | Path, dict[str, Any] | list[dict[str, Any]]]) -> None:\n        super().__init__(definitions=definitions)\n\n        self.source = GraphSource.ANSIBLE\n\n    def _create_vertices(self) -> None:\n        for file_path, definition in self.definitions.items():\n            if not isinstance(definition, list):\n                logging.debug(f\"definition of file {file_path} has the wrong type {type(definition)}\")\n                continue\n\n            file_path = str(file_path)\n\n            for code_block in definition:\n                if ResourceType.TASKS in code_block:\n                    for task in code_block[ResourceType.TASKS]:\n                        self._process_blocks(file_path=file_path, task=task)\n                else:\n                    self._process_blocks(file_path=file_path, task=code_block)\n\n    def _process_blocks(self, file_path: str, task: Any, prefix: str = \"\") -> None:\n        \"\"\"Checks for possible block usage\"\"\"\n\n        if not task or not isinstance(task, dict):\n            return\n\n        if \"block\" in task and isinstance(task[\"block\"], list):\n            prefix += f\"{ResourceType.BLOCK}.\"  # with each nested level an extra block prefix is added\n            self._create_block_vertices(file_path=file_path, block=task, prefix=prefix)\n\n            for block_task in task[\"block\"]:\n                self._process_blocks(file_path=file_path, task=block_task, prefix=prefix)\n        else:\n            self._create_tasks_vertices(file_path=file_path, task=task, prefix=prefix)\n\n    def _create_tasks_vertices(self, file_path: str, task: Any, prefix: str = \"\") -> None:\n        \"\"\"Creates tasks vertices\"\"\"\n\n        if not task or not isinstance(task, dict):\n            return\n\n        # grab the task name at the beginning before trying to find the actual module name\n        task_name = task.get(\"name\") or \"unknown\"\n\n        for name, config in task.items():\n            if name in TASK_RESERVED_KEYWORDS:\n                continue\n            if name in (START_LINE, END_LINE):\n                continue\n\n            resource_type = f\"{ResourceType.TASKS}.{name}\"\n\n            if isinstance(config, str):\n                # this happens when modules have no parameters and are directly used with the user input\n                # ex. ansible.builtin.command: cat /etc/passwd\n                config = {SELF_REFERENCE: config}\n            elif config is None:\n                # this happens when modules have no parameters and are passed no value\n                # ex. amazon.aws.ec2_instance_info:\n                config = {\n                    START_LINE: task[START_LINE],\n                    END_LINE: task[END_LINE],\n                }\n\n            attributes = deepcopy(config)\n            attributes[CustomAttributes.RESOURCE_TYPE] = resource_type\n\n            # only the module code is relevant for validation,\n            # but in the check result the whole task should be visible\n            attributes[START_LINE] = task[START_LINE]\n            attributes[END_LINE] = task[END_LINE]\n\n            self.vertices.append(\n                Block(\n                    name=f\"{resource_type}.{task_name}\",\n                    config=config,\n                    path=file_path,\n                    block_type=BlockType.RESOURCE,\n                    attributes=attributes,\n                    id=f\"{resource_type}.{prefix}{task_name}\",\n                    source=self.source,\n                )\n            )\n\n            # no need to further check\n            break\n\n    def _create_block_vertices(self, file_path: str, block: dict[str, Any], prefix: str = \"\") -> None:\n        \"\"\"Creates block vertices\"\"\"\n\n        # grab the block name, if it exists\n        block_name = block.get(\"name\") or \"unknown\"\n\n        config = block\n        attributes = deepcopy(config)\n        attributes[CustomAttributes.RESOURCE_TYPE] = ResourceType.BLOCK\n        del attributes[ResourceType.BLOCK]  # the real block content are tasks, which have their own vertices\n\n        self.vertices.append(\n            Block(\n                name=f\"{ResourceType.BLOCK}.{block_name}\",\n                config=config,\n                path=file_path,\n                block_type=BlockType.RESOURCE,\n                attributes=attributes,\n                id=f\"{prefix}{block_name}\",\n                source=self.source,\n            )\n        )\n\n    def _create_edges(self) -> None:\n        return None\n\n    @staticmethod\n    def get_files_definitions(root_folder: str | Path) -> dict[str | Path, dict[str, Any] | list[dict[str, Any]]]:\n        definitions: \"dict[str | Path, dict[str, Any] | list[dict[str, Any]]]\" = {}\n        file_paths = get_scannable_file_paths(root_folder=root_folder)\n\n        for file_path in file_paths:\n            result = parse_file(f=file_path)\n            if result is not None:\n                definitions[file_path] = result[0]\n\n        return definitions\n", "path": "checkov/ansible/graph_builder/local_graph.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport logging\nfrom copy import deepcopy\nfrom pathlib import Path\nfrom typing import Any\n\nfrom checkov.common.graph.graph_builder import CustomAttributes\nfrom checkov.common.graph.graph_builder.consts import GraphSource, SELF_REFERENCE\nfrom checkov.common.graph.graph_builder.graph_components.block_types import BlockType\nfrom checkov.common.graph.graph_builder.graph_components.blocks import Block\nfrom checkov.common.runners.graph_builder.local_graph import ObjectLocalGraph\nfrom checkov.common.util.consts import START_LINE, END_LINE\nfrom checkov.ansible.graph_builder.graph_components.resource_types import ResourceType\nfrom checkov.ansible.utils import get_scannable_file_paths, TASK_RESERVED_KEYWORDS, parse_file\n\n\nclass AnsibleLocalGraph(ObjectLocalGraph):\n    def __init__(self, definitions: dict[str | Path, dict[str, Any] | list[dict[str, Any]]]) -> None:\n        super().__init__(definitions=definitions)\n\n        self.source = GraphSource.ANSIBLE\n\n    def _create_vertices(self) -> None:\n        for file_path, definition in self.definitions.items():\n            if not isinstance(definition, list):\n                logging.debug(f\"definition of file {file_path} has the wrong type {type(definition)}\")\n                continue\n\n            file_path = str(file_path)\n\n            for code_block in definition:\n                if ResourceType.TASKS in code_block:\n                    for task in code_block[ResourceType.TASKS]:\n                        self._process_blocks(file_path=file_path, task=task)\n                else:\n                    self._process_blocks(file_path=file_path, task=code_block)\n\n    def _process_blocks(self, file_path: str, task: Any, prefix: str = \"\") -> None:\n        \"\"\"Checks for possible block usage\"\"\"\n\n        if not task or not isinstance(task, dict):\n            return\n\n        if ResourceType.BLOCK in task and isinstance(task[ResourceType.BLOCK], list):\n            prefix += f\"{ResourceType.BLOCK}.\"  # with each nested level an extra block prefix is added\n            self._create_block_vertices(file_path=file_path, block=task, prefix=prefix)\n\n            for block_task in task[ResourceType.BLOCK]:\n                self._process_blocks(file_path=file_path, task=block_task, prefix=prefix)\n        else:\n            self._create_tasks_vertices(file_path=file_path, task=task, prefix=prefix)\n\n    def _create_tasks_vertices(self, file_path: str, task: Any, prefix: str = \"\") -> None:\n        \"\"\"Creates tasks vertices\"\"\"\n\n        if not task or not isinstance(task, dict):\n            return\n\n        # grab the task name at the beginning before trying to find the actual module name\n        task_name = task.get(\"name\") or \"unknown\"\n\n        for name, config in task.items():\n            if name in TASK_RESERVED_KEYWORDS:\n                continue\n            if name in (START_LINE, END_LINE):\n                continue\n            if isinstance(config, list):\n                # either it is actually not an Ansible file or a playbook without tasks refs\n                continue\n\n            resource_type = f\"{ResourceType.TASKS}.{name}\"\n\n            if isinstance(config, str):\n                # this happens when modules have no parameters and are directly used with the user input\n                # ex. ansible.builtin.command: cat /etc/passwd\n                config = {SELF_REFERENCE: config}\n            elif config is None:\n                # this happens when modules have no parameters and are passed no value\n                # ex. amazon.aws.ec2_instance_info:\n                config = {\n                    START_LINE: task[START_LINE],\n                    END_LINE: task[END_LINE],\n                }\n\n            attributes = deepcopy(config)\n            attributes[CustomAttributes.RESOURCE_TYPE] = resource_type\n\n            # only the module code is relevant for validation,\n            # but in the check result the whole task should be visible\n            attributes[START_LINE] = task[START_LINE]\n            attributes[END_LINE] = task[END_LINE]\n\n            self.vertices.append(\n                Block(\n                    name=f\"{resource_type}.{task_name}\",\n                    config=config,\n                    path=file_path,\n                    block_type=BlockType.RESOURCE,\n                    attributes=attributes,\n                    id=f\"{resource_type}.{prefix}{task_name}\",\n                    source=self.source,\n                )\n            )\n\n            # no need to further check\n            break\n\n    def _create_block_vertices(self, file_path: str, block: dict[str, Any], prefix: str = \"\") -> None:\n        \"\"\"Creates block vertices\"\"\"\n\n        # grab the block name, if it exists\n        block_name = block.get(\"name\") or \"unknown\"\n\n        config = block\n        attributes = deepcopy(config)\n        attributes[CustomAttributes.RESOURCE_TYPE] = ResourceType.BLOCK\n        del attributes[ResourceType.BLOCK]  # the real block content are tasks, which have their own vertices\n\n        self.vertices.append(\n            Block(\n                name=f\"{ResourceType.BLOCK}.{block_name}\",\n                config=config,\n                path=file_path,\n                block_type=BlockType.RESOURCE,\n                attributes=attributes,\n                id=f\"{prefix}{block_name}\",\n                source=self.source,\n            )\n        )\n\n    def _create_edges(self) -> None:\n        return None\n\n    @staticmethod\n    def get_files_definitions(root_folder: str | Path) -> dict[str | Path, dict[str, Any] | list[dict[str, Any]]]:\n        definitions: \"dict[str | Path, dict[str, Any] | list[dict[str, Any]]]\" = {}\n        file_paths = get_scannable_file_paths(root_folder=root_folder)\n\n        for file_path in file_paths:\n            result = parse_file(f=file_path)\n            if result is not None:\n                definitions[file_path] = result[0]\n\n        return definitions\n", "path": "checkov/ansible/graph_builder/local_graph.py"}]}