Improve parsing of contract's comment

pull/1734/head
Feist Josselin 2 years ago
parent 36052fdc4e
commit 76d8321401
  1. 27
      slither/core/declarations/contract.py
  2. 27
      slither/solc_parsing/declarations/contract.py
  3. 7
      tests/custom_comments/contract_comment.sol
  4. 24
      tests/test_features.py

@ -110,6 +110,8 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
Dict["StateVariable", Set[Union["StateVariable", "Function"]]] Dict["StateVariable", Set[Union["StateVariable", "Function"]]]
] = None ] = None
self._comments: Optional[str] = None
################################################################################### ###################################################################################
################################################################################### ###################################################################################
# region General's properties # region General's properties
@ -165,6 +167,31 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def is_library(self, is_library: bool): def is_library(self, is_library: bool):
self._is_library = is_library self._is_library = is_library
@property
def comments(self) -> Optional[str]:
"""
Return the comments associated with the contract.
When using comments, avoid strict text matching, as the solc behavior might change.
For example, for old solc version, the first space after the * is not kept, i.e:
* @title Test Contract
* @dev Test comment
Returns
- " @title Test Contract\n @dev Test comment" for newest versions
- "@title Test Contract\n@dev Test comment" for older versions
Returns:
the comment as a string
"""
return self._comments
@comments.setter
def comments(self, comments: str):
self._comments = comments
# endregion # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################

@ -780,12 +780,35 @@ class ContractSolc(CallerContextExpression):
self._customErrorParsed = [] self._customErrorParsed = []
def _handle_comment(self, attributes: Dict) -> None: def _handle_comment(self, attributes: Dict) -> None:
"""
Save the contract comment in self.comments
And handle custom slither comments
Args:
attributes:
Returns:
"""
# Old solc versions store the comment in attributes["documentation"]
# More recent ones store it in attributes["documentation"]["text"]
if ( if (
"documentation" in attributes "documentation" in attributes
and attributes["documentation"] is not None and attributes["documentation"] is not None
and "text" in attributes["documentation"] and (
"text" in attributes["documentation"]
or isinstance(attributes["documentation"], str)
)
): ):
candidates = attributes["documentation"]["text"].replace("\n", ",").split(",") text = (
attributes["documentation"]
if isinstance(attributes["documentation"], str)
else attributes["documentation"]["text"]
)
self._contract.comments = text
# Look for custom comments
candidates = text.replace("\n", ",").split(",")
for candidate in candidates: for candidate in candidates:
if "@custom:security isDelegatecallProxy" in candidate: if "@custom:security isDelegatecallProxy" in candidate:

@ -0,0 +1,7 @@
/**
* @title Test Contract
* @dev Test comment
*/
contract A{
}

@ -31,7 +31,7 @@ def test_node() -> None:
def test_collision() -> None: def test_collision() -> None:
solc_select.switch_global_version("0.8.0", always_install=True)
standard_json = SolcStandardJson() standard_json = SolcStandardJson()
standard_json.add_source_file("./tests/collisions/a.sol") standard_json.add_source_file("./tests/collisions/a.sol")
standard_json.add_source_file("./tests/collisions/b.sol") standard_json.add_source_file("./tests/collisions/b.sol")
@ -43,6 +43,7 @@ def test_collision() -> None:
def test_cycle() -> None: def test_cycle() -> None:
solc_select.switch_global_version("0.8.0", always_install=True)
slither = Slither("./tests/test_cyclic_import/a.sol") slither = Slither("./tests/test_cyclic_import/a.sol")
_run_all_detectors(slither) _run_all_detectors(slither)
@ -74,6 +75,27 @@ def test_upgradeable_comments() -> None:
assert v1.upgradeable_version == "version_1" assert v1.upgradeable_version == "version_1"
def test_contract_comments() -> None:
comments = " @title Test Contract\n @dev Test comment"
solc_select.switch_global_version("0.8.10", always_install=True)
slither = Slither("./tests/custom_comments/contract_comment.sol")
compilation_unit = slither.compilation_units[0]
contract = compilation_unit.get_contract_from_name("A")[0]
assert contract.comments == comments
# Old solc versions have a different parsing of comments
# the initial space (after *) is also not kept on every line
comments = "@title Test Contract\n@dev Test comment"
solc_select.switch_global_version("0.5.16", always_install=True)
slither = Slither("./tests/custom_comments/contract_comment.sol")
compilation_unit = slither.compilation_units[0]
contract = compilation_unit.get_contract_from_name("A")[0]
assert contract.comments == comments
def test_using_for_top_level_same_name() -> None: def test_using_for_top_level_same_name() -> None:
solc_select.switch_global_version("0.8.15", always_install=True) solc_select.switch_global_version("0.8.15", always_install=True)
slither = Slither("./tests/ast-parsing/using-for-3-0.8.0.sol") slither = Slither("./tests/ast-parsing/using-for-3-0.8.0.sol")

Loading…
Cancel
Save