Repository: crytic/slither

Issue:
expose virtual and override attributes from AST in solidity `Function` 
### Describe the desired feature

This is missing and would be useful e.g. we can remove virtual functions that aren't overridden from the inheritance graph


Discussion:

Resolving patch:
diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py
--- a/slither/core/declarations/function.py
+++ b/slither/core/declarations/function.py
@@ -126,6 +126,8 @@ def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None:
         self._pure: bool = False
         self._payable: bool = False
         self._visibility: Optional[str] = None
+        self._virtual: bool = False
+        self._overrides: Optional[List[int]] = None
 
         self._is_implemented: Optional[bool] = None
         self._is_empty: Optional[bool] = None
@@ -441,6 +443,44 @@ def payable(self) -> bool:
     def payable(self, p: bool):
         self._payable = p
 
+    # endregion
+    ###################################################################################
+    ###################################################################################
+    # region Virtual
+    ###################################################################################
+    ###################################################################################
+
+    @property
+    def virtual(self) -> bool:
+        """
+        Note for Solidity < 0.6.0 it will always be false
+        bool: True if the function is virtual
+        """
+        return self._virtual
+
+    @virtual.setter
+    def virtual(self, v: bool):
+        self._virtual = v
+
+    @property
+    def override(self) -> bool:
+        """
+        Note for Solidity < 0.6.0 it will always be false
+        bool: True if the function overrides a base function
+        """
+        return self._overrides is not None
+
+    @property
+    def overrides(self) -> Optional[List[int]]:
+        """
+        Optional[List[int]]: List of the overridden functions id
+        """
+        return self._overrides
+
+    @overrides.setter
+    def overrides(self, o: List[Tuple[str, str]]):
+        self._overrides = o
+
     # endregion
     ###################################################################################
     ###################################################################################
diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py
--- a/slither/solc_parsing/declarations/function.py
+++ b/slither/solc_parsing/declarations/function.py
@@ -207,8 +207,6 @@ def _analyze_attributes(self) -> None:
         else:
             attributes = self._functionNotParsed["attributes"]
 
-        if "payable" in attributes:
-            self._function.payable = attributes["payable"]
         if "stateMutability" in attributes:
             if attributes["stateMutability"] == "payable":
                 self._function.payable = True
@@ -242,6 +240,15 @@ def _analyze_attributes(self) -> None:
         if "payable" in attributes:
             self._function.payable = attributes["payable"]
 
+        if "baseFunctions" in attributes:
+            overrides = []
+            for o in attributes["baseFunctions"]:
+                overrides.append(o)
+            self._function.overrides = overrides
+
+        if "virtual" in attributes:
+            self._function.virtual = attributes["virtual"]
+
     def analyze_params(self) -> None:
         # Can be re-analyzed due to inheritance
         if self._params_was_analyzed:


Test patch: