Merge branch 'dev' into fix/guard-implicit-conversion-of-literals

pull/2383/head
alpharush 7 months ago committed by GitHub
commit 6bda75df3b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      .github/workflows/ci.yml
  2. 2
      .github/workflows/doctor.yml
  3. 40
      .github/workflows/issue-metrics.yml
  4. 2
      .github/workflows/publish.yml
  5. 2
      .github/workflows/test.yml
  6. 23
      slither/__main__.py
  7. 1
      slither/core/declarations/solidity_variables.py
  8. 6
      slither/core/scope/scope.py
  9. 7
      slither/slithir/convert.py
  10. 14
      slither/slithir/operations/new_contract.py
  11. 4
      slither/solc_parsing/declarations/contract.py
  12. 20
      slither/solc_parsing/declarations/event_contract.py
  13. 75
      slither/solc_parsing/declarations/event_top_level.py
  14. 17
      slither/solc_parsing/expressions/expression_parsing.py
  15. 10
      slither/solc_parsing/expressions/find_variable.py
  16. 17
      slither/solc_parsing/slither_compilation_unit_solc.py
  17. 11
      slither/solc_parsing/solidity_types/type_parsing.py
  18. 4
      slither/utils/output.py
  19. 15
      slither/vyper_parsing/expressions/expression_parsing.py
  20. 2
      tests/e2e/solc_parsing/test_ast_parsing.py
  21. 1
      tests/e2e/solc_parsing/test_data/aliasing/MyContract.sol
  22. 8
      tests/e2e/solc_parsing/test_data/aliasing/alias-symbol-NewContract.sol
  23. 9
      tests/e2e/solc_parsing/test_data/aliasing/alias-unit-NewContract.sol
  24. BIN
      tests/e2e/solc_parsing/test_data/compile/aliasing/alias-symbol-NewContract.sol-0.8.19-compact.zip
  25. BIN
      tests/e2e/solc_parsing/test_data/compile/aliasing/alias-unit-NewContract.sol-0.8.19-compact.zip
  26. BIN
      tests/e2e/solc_parsing/test_data/compile/event-top-level.sol-0.8.22-compact.zip
  27. 10
      tests/e2e/solc_parsing/test_data/event-top-level.sol
  28. 6
      tests/e2e/solc_parsing/test_data/expected/aliasing/alias-symbol-NewContract.sol-0.8.19-compact.json
  29. 6
      tests/e2e/solc_parsing/test_data/expected/aliasing/alias-unit-NewContract.sol-0.8.19-compact.json
  30. 3
      tests/e2e/solc_parsing/test_data/expected/event-top-level.sol-0.8.22-compact.json
  31. 24
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_c__0.txt
  32. 8
      tests/e2e/vyper_parsing/snapshots/ast_parsing__vyper_cfgir_builtins_test_builtins__0.txt
  33. 1
      tests/e2e/vyper_parsing/test_data/builtins.vy
  34. 124
      tests/unit/slithir/test_implicit_returns.py

@ -26,7 +26,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: ["ubuntu-latest", "windows-2022"] os: ["ubuntu-latest", "windows-2022"]
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.11"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11"]') }} python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.12"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11", "3.12"]') }}
type: ["cli", type: ["cli",
"dapp", "dapp",
"data_dependency", "data_dependency",
@ -67,7 +67,7 @@ jobs:
- name: Set up nix - name: Set up nix
if: matrix.type == 'dapp' if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v25 uses: cachix/install-nix-action@v26
- name: Set up cachix - name: Set up cachix
if: matrix.type == 'dapp' if: matrix.type == 'dapp'

@ -23,7 +23,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: ["ubuntu-latest", "windows-2022"] os: ["ubuntu-latest", "windows-2022"]
python: ["3.8", "3.9", "3.10", "3.11"] python: ["3.8", "3.9", "3.10", "3.11", "3.12"]
exclude: exclude:
# strange failure # strange failure
- os: windows-2022 - os: windows-2022

@ -0,0 +1,40 @@
name: Monthly issue metrics
on:
workflow_dispatch:
schedule:
- cron: '3 2 1 * *'
permissions:
issues: write
pull-requests: read
jobs:
build:
name: issue metrics
runs-on: ubuntu-latest
steps:
- name: Get dates for last month
shell: bash
run: |
# Calculate the first day of the previous month
first_day=$(date -d "last month" +%Y-%m-01)
# Calculate the last day of the previous month
last_day=$(date -d "$first_day +1 month -1 day" +%Y-%m-%d)
#Set an environment variable with the date range
echo "$first_day..$last_day"
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
- name: Run issue-metrics tool
uses: github/issue-metrics@v2
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SEARCH_QUERY: 'repo:crytic/slither is:issue created:${{ env.last_month }} -reason:"not planned" -reason:"duplicate"'
- name: Create issue
uses: peter-evans/create-issue-from-file@v5
with:
title: Monthly issue metrics report
token: ${{ secrets.GITHUB_TOKEN }}
content-filepath: ./issue_metrics.md

@ -44,7 +44,7 @@ jobs:
path: dist/ path: dist/
- name: publish - name: publish
uses: pypa/gh-action-pypi-publish@v1.8.11 uses: pypa/gh-action-pypi-publish@v1.8.14
- name: sign - name: sign
uses: sigstore/gh-action-sigstore-python@v2.1.1 uses: sigstore/gh-action-sigstore-python@v2.1.1

@ -25,7 +25,7 @@ jobs:
matrix: matrix:
os: ["ubuntu-latest", "windows-2022"] os: ["ubuntu-latest", "windows-2022"]
type: ["unit", "integration", "tool"] type: ["unit", "integration", "tool"]
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.11"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11"]') }} python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.12"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11", "3.12"]') }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }} - name: Set up Python ${{ matrix.python }}

@ -10,9 +10,9 @@ import os
import pstats import pstats
import sys import sys
import traceback import traceback
from importlib import metadata
from typing import Tuple, Optional, List, Dict, Type, Union, Any, Sequence from typing import Tuple, Optional, List, Dict, Type, Union, Any, Sequence
from pkg_resources import iter_entry_points, require
from crytic_compile import cryticparser, CryticCompile from crytic_compile import cryticparser, CryticCompile
from crytic_compile.platform.standard import generate_standard_export from crytic_compile.platform.standard import generate_standard_export
@ -166,19 +166,26 @@ def get_detectors_and_printers() -> Tuple[
printers = [p for p in printers_ if inspect.isclass(p) and issubclass(p, AbstractPrinter)] printers = [p for p in printers_ if inspect.isclass(p) and issubclass(p, AbstractPrinter)]
# Handle plugins! # Handle plugins!
for entry_point in iter_entry_points(group="slither_analyzer.plugin", name=None): if sys.version_info >= (3, 10):
entry_points = metadata.entry_points(group="slither_analyzer.plugin")
else:
from pkg_resources import iter_entry_points # pylint: disable=import-outside-toplevel
entry_points = iter_entry_points(group="slither_analyzer.plugin", name=None)
for entry_point in entry_points:
make_plugin = entry_point.load() make_plugin = entry_point.load()
plugin_detectors, plugin_printers = make_plugin() plugin_detectors, plugin_printers = make_plugin()
detector = None detector = None
if not all(issubclass(detector, AbstractDetector) for detector in plugin_detectors): if not all(issubclass(detector, AbstractDetector) for detector in plugin_detectors):
raise Exception( raise ValueError(
f"Error when loading plugin {entry_point}, {detector} is not a detector" f"Error when loading plugin {entry_point}, {detector} is not a detector"
) )
printer = None printer = None
if not all(issubclass(printer, AbstractPrinter) for printer in plugin_printers): if not all(issubclass(printer, AbstractPrinter) for printer in plugin_printers):
raise Exception(f"Error when loading plugin {entry_point}, {printer} is not a printer") raise ValueError(f"Error when loading plugin {entry_point}, {printer} is not a printer")
# We convert those to lists in case someone returns a tuple # We convert those to lists in case someone returns a tuple
detectors += list(plugin_detectors) detectors += list(plugin_detectors)
@ -208,7 +215,7 @@ def choose_detectors(
if detector in detectors: if detector in detectors:
detectors_to_run.append(detectors[detector]) detectors_to_run.append(detectors[detector])
else: else:
raise Exception(f"Error: {detector} is not a detector") raise ValueError(f"Error: {detector} is not a detector")
detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT) detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
return detectors_to_run return detectors_to_run
@ -256,7 +263,7 @@ def choose_printers(
if printer in printers: if printer in printers:
printers_to_run.append(printers[printer]) printers_to_run.append(printers[printer])
else: else:
raise Exception(f"Error: {printer} is not a printer") raise ValueError(f"Error: {printer} is not a printer")
return printers_to_run return printers_to_run
@ -298,7 +305,7 @@ def parse_args(
parser.add_argument( parser.add_argument(
"--version", "--version",
help="displays the current version", help="displays the current version",
version=require("slither-analyzer")[0].version, version=metadata.version("slither-analyzer"),
action="version", action="version",
) )
@ -648,7 +655,7 @@ def parse_args(
args.json_types = set(args.json_types.split(",")) # type:ignore args.json_types = set(args.json_types.split(",")) # type:ignore
for json_type in args.json_types: for json_type in args.json_types:
if json_type not in JSON_OUTPUT_TYPES: if json_type not in JSON_OUTPUT_TYPES:
raise Exception(f'Error: "{json_type}" is not a valid JSON result output type.') raise ValueError(f'Error: "{json_type}" is not a valid JSON result output type.')
return args return args

@ -116,6 +116,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"_abi_encode()": [], "_abi_encode()": [],
"slice()": [], "slice()": [],
"uint2str()": ["string"], "uint2str()": ["string"],
"send()": [],
} }

@ -36,11 +36,11 @@ class FileScope:
# So we simplify the logic and have the scope fields all populated # So we simplify the logic and have the scope fields all populated
self.custom_errors: Set[CustomErrorTopLevel] = set() self.custom_errors: Set[CustomErrorTopLevel] = set()
self.enums: Dict[str, EnumTopLevel] = {} self.enums: Dict[str, EnumTopLevel] = {}
self.events: Dict[str, EventTopLevel] = {}
# Functions is a list instead of a dict # Functions is a list instead of a dict
# Because we parse the function signature later on # Because we parse the function signature later on
# So we simplify the logic and have the scope fields all populated # So we simplify the logic and have the scope fields all populated
self.functions: Set[FunctionTopLevel] = set() self.functions: Set[FunctionTopLevel] = set()
self.events: Set[EventTopLevel] = set()
self.using_for_directives: Set[UsingForTopLevel] = set() self.using_for_directives: Set[UsingForTopLevel] = set()
self.imports: Set[Import] = set() self.imports: Set[Import] = set()
self.pragmas: Set[Pragma] = set() self.pragmas: Set[Pragma] = set()
@ -76,8 +76,8 @@ class FileScope:
if not _dict_contain(new_scope.enums, self.enums): if not _dict_contain(new_scope.enums, self.enums):
self.enums.update(new_scope.enums) self.enums.update(new_scope.enums)
learn_something = True learn_something = True
if not _dict_contain(new_scope.events, self.events): if not new_scope.events.issubset(self.events):
self.events.update(new_scope.events) self.events |= new_scope.events
learn_something = True learn_something = True
if not new_scope.functions.issubset(self.functions): if not new_scope.functions.issubset(self.functions):
self.functions |= new_scope.functions self.functions |= new_scope.functions

@ -871,9 +871,7 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
elif isinstance(ir, NewArray): elif isinstance(ir, NewArray):
ir.lvalue.set_type(ir.array_type) ir.lvalue.set_type(ir.array_type)
elif isinstance(ir, NewContract): elif isinstance(ir, NewContract):
contract = node.file_scope.get_contract_from_name(ir.contract_name) ir.lvalue.set_type(ir.contract_name)
assert contract
ir.lvalue.set_type(UserDefinedType(contract))
elif isinstance(ir, NewElementaryType): elif isinstance(ir, NewElementaryType):
ir.lvalue.set_type(ir.type) ir.lvalue.set_type(ir.type)
elif isinstance(ir, NewStructure): elif isinstance(ir, NewStructure):
@ -1164,7 +1162,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
return n return n
if isinstance(ins.ori, TmpNewContract): if isinstance(ins.ori, TmpNewContract):
op = NewContract(Constant(ins.ori.contract_name), ins.lvalue) op = NewContract(ins.ori.contract_name, ins.lvalue)
op.set_expression(ins.expression) op.set_expression(ins.expression)
op.call_id = ins.call_id op.call_id = ins.call_id
if ins.call_value: if ins.call_value:
@ -1719,6 +1717,7 @@ def convert_type_of_high_and_internal_level_call(
Returns: Returns:
Potential new IR Potential new IR
""" """
func = None func = None
if isinstance(ir, InternalCall): if isinstance(ir, InternalCall):
candidates: List[Function] candidates: List[Function]

@ -3,6 +3,7 @@ from typing import Optional, Any, List, Union
from slither.core.declarations import Function from slither.core.declarations import Function
from slither.core.declarations.contract import Contract from slither.core.declarations.contract import Contract
from slither.core.variables import Variable from slither.core.variables import Variable
from slither.core.solidity_types import UserDefinedType
from slither.slithir.operations import Call, OperationWithLValue from slither.slithir.operations import Call, OperationWithLValue
from slither.slithir.utils.utils import is_valid_lvalue from slither.slithir.utils.utils import is_valid_lvalue
from slither.slithir.variables.constant import Constant from slither.slithir.variables.constant import Constant
@ -13,7 +14,7 @@ from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA
class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instance-attributes class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instance-attributes
def __init__( def __init__(
self, self,
contract_name: Constant, contract_name: UserDefinedType,
lvalue: Union[TemporaryVariableSSA, TemporaryVariable], lvalue: Union[TemporaryVariableSSA, TemporaryVariable],
names: Optional[List[str]] = None, names: Optional[List[str]] = None,
) -> None: ) -> None:
@ -23,7 +24,9 @@ class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instan
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order. For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None. Otherwise, None.
""" """
assert isinstance(contract_name, Constant) assert isinstance(
contract_name.type, Contract
), f"contract_name is {contract_name} of type {type(contract_name)}"
assert is_valid_lvalue(lvalue) assert is_valid_lvalue(lvalue)
super().__init__(names=names) super().__init__(names=names)
self._contract_name = contract_name self._contract_name = contract_name
@ -58,7 +61,7 @@ class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instan
self._call_salt = s self._call_salt = s
@property @property
def contract_name(self) -> Constant: def contract_name(self) -> UserDefinedType:
return self._contract_name return self._contract_name
@property @property
@ -69,10 +72,7 @@ class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instan
@property @property
def contract_created(self) -> Contract: def contract_created(self) -> Contract:
contract_name = self.contract_name return self.contract_name.type
contract_instance = self.node.file_scope.get_contract_from_name(contract_name)
assert contract_instance
return contract_instance
################################################################################### ###################################################################################
################################################################################### ###################################################################################

@ -16,7 +16,7 @@ from slither.core.solidity_types import ElementaryType, TypeAliasContract
from slither.core.variables.state_variable import StateVariable from slither.core.variables.state_variable import StateVariable
from slither.solc_parsing.declarations.caller_context import CallerContextExpression from slither.solc_parsing.declarations.caller_context import CallerContextExpression
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
from slither.solc_parsing.declarations.event import EventSolc from slither.solc_parsing.declarations.event_contract import EventContractSolc
from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.modifier import ModifierSolc from slither.solc_parsing.declarations.modifier import ModifierSolc
from slither.solc_parsing.declarations.structure_contract import StructureContractSolc from slither.solc_parsing.declarations.structure_contract import StructureContractSolc
@ -760,7 +760,7 @@ class ContractSolc(CallerContextExpression):
event.set_contract(self._contract) event.set_contract(self._contract)
event.set_offset(event_to_parse["src"], self._contract.compilation_unit) event.set_offset(event_to_parse["src"], self._contract.compilation_unit)
event_parser = EventSolc(event, event_to_parse, self._slither_parser) # type: ignore event_parser = EventContractSolc(event, event_to_parse, self) # type: ignore
event_parser.analyze() # type: ignore event_parser.analyze() # type: ignore
self._contract.events_as_dict[event.full_name] = event self._contract.events_as_dict[event.full_name] = event
except (VariableNotFound, KeyError) as e: except (VariableNotFound, KeyError) as e:

@ -1,27 +1,27 @@
""" """
Event module EventContract module
""" """
from typing import TYPE_CHECKING, Dict from typing import TYPE_CHECKING, Dict
from slither.core.variables.event_variable import EventVariable from slither.core.variables.event_variable import EventVariable
from slither.solc_parsing.variables.event_variable import EventVariableSolc from slither.solc_parsing.variables.event_variable import EventVariableSolc
from slither.core.declarations.event import Event from slither.core.declarations.event_contract import EventContract
if TYPE_CHECKING: if TYPE_CHECKING:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc from slither.solc_parsing.declarations.contract import ContractSolc
class EventSolc: class EventContractSolc:
""" """
Event class EventContract class
""" """
def __init__( def __init__(
self, event: Event, event_data: Dict, slither_parser: "SlitherCompilationUnitSolc" self, event: EventContract, event_data: Dict, contract_parser: "ContractSolc"
) -> None: ) -> None:
self._event = event self._event = event
self._slither_parser = slither_parser self._contract_parser = contract_parser
if self.is_compact_ast: if self.is_compact_ast:
self._event.name = event_data["name"] self._event.name = event_data["name"]
@ -42,16 +42,16 @@ class EventSolc:
@property @property
def is_compact_ast(self) -> bool: def is_compact_ast(self) -> bool:
return self._slither_parser.is_compact_ast return self._contract_parser.is_compact_ast
def analyze(self) -> None: def analyze(self) -> None:
for elem_to_parse in self._elemsNotParsed: for elem_to_parse in self._elemsNotParsed:
elem = EventVariable() elem = EventVariable()
# Todo: check if the source offset is always here # Todo: check if the source offset is always here
if "src" in elem_to_parse: if "src" in elem_to_parse:
elem.set_offset(elem_to_parse["src"], self._slither_parser.compilation_unit) elem.set_offset(elem_to_parse["src"], self._contract_parser.compilation_unit)
elem_parser = EventVariableSolc(elem, elem_to_parse) elem_parser = EventVariableSolc(elem, elem_to_parse)
elem_parser.analyze(self._slither_parser) elem_parser.analyze(self._contract_parser)
self._event.elems.append(elem) self._event.elems.append(elem)

@ -0,0 +1,75 @@
"""
EventTopLevel module
"""
from typing import TYPE_CHECKING, Dict
from slither.core.declarations.event_top_level import EventTopLevel
from slither.core.variables.event_variable import EventVariable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.solc_parsing.variables.event_variable import EventVariableSolc
from slither.solc_parsing.declarations.caller_context import CallerContextExpression
if TYPE_CHECKING:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
class EventTopLevelSolc(CallerContextExpression):
"""
EventTopLevel class
"""
def __init__(
self, event: EventTopLevel, event_data: Dict, slither_parser: "SlitherCompilationUnitSolc"
) -> None:
self._event = event
self._slither_parser = slither_parser
if self.is_compact_ast:
self._event.name = event_data["name"]
elems = event_data["parameters"]
assert elems["nodeType"] == "ParameterList"
self._elemsNotParsed = elems["parameters"]
else:
self._event.name = event_data["attributes"]["name"]
for elem in event_data["children"]:
# From Solidity 0.6.3 to 0.6.10 (included)
# Comment above a event might be added in the children
# of an event for the legacy ast
if elem["name"] == "ParameterList":
if "children" in elem:
self._elemsNotParsed = elem["children"]
else:
self._elemsNotParsed = []
def analyze(self) -> None:
for elem_to_parse in self._elemsNotParsed:
elem = EventVariable()
# Todo: check if the source offset is always here
if "src" in elem_to_parse:
elem.set_offset(elem_to_parse["src"], self._slither_parser.compilation_unit)
elem_parser = EventVariableSolc(elem, elem_to_parse)
elem_parser.analyze(self)
self._event.elems.append(elem)
self._elemsNotParsed = []
@property
def is_compact_ast(self) -> bool:
return self._slither_parser.is_compact_ast
@property
def compilation_unit(self) -> SlitherCompilationUnit:
return self._slither_parser.compilation_unit
def get_key(self) -> str:
return self._slither_parser.get_key()
@property
def slither_parser(self) -> "SlitherCompilationUnitSolc":
return self._slither_parser
@property
def underlying_event(self) -> EventTopLevel:
return self._event

@ -614,20 +614,9 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
assert type_name[caller_context.get_key()] == "UserDefinedTypeName" assert type_name[caller_context.get_key()] == "UserDefinedTypeName"
if is_compact_ast: contract_type = parse_type(type_name, caller_context)
assert isinstance(contract_type, UserDefinedType)
# Changed introduced in Solidity 0.8 new = NewContract(contract_type)
# see https://github.com/crytic/slither/issues/794
# TODO explore more the changes introduced in 0.8 and the usage of pathNode/IdentifierPath
if "name" not in type_name:
assert "pathNode" in type_name and "name" in type_name["pathNode"]
contract_name = type_name["pathNode"]["name"]
else:
contract_name = type_name["name"]
else:
contract_name = type_name["attributes"]["name"]
new = NewContract(contract_name)
new.set_offset(src, caller_context.compilation_unit) new.set_offset(src, caller_context.compilation_unit)
return new return new

@ -134,8 +134,9 @@ def find_top_level(
if var_name in scope.enums: if var_name in scope.enums:
return scope.enums[var_name], False return scope.enums[var_name], False
if var_name in scope.events: for event in scope.events:
return scope.events[var_name], False if var_name == event.full_name:
return event, False
for import_directive in scope.imports: for import_directive in scope.imports:
if import_directive.alias == var_name: if import_directive.alias == var_name:
@ -268,6 +269,7 @@ def _find_variable_init(
from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc
from slither.solc_parsing.declarations.event_top_level import EventTopLevelSolc
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
direct_contracts: List[Contract] direct_contracts: List[Contract]
@ -311,6 +313,10 @@ def _find_variable_init(
direct_contracts = [] direct_contracts = []
direct_functions_parser = [] direct_functions_parser = []
scope = caller_context.underlying_variable.file_scope scope = caller_context.underlying_variable.file_scope
elif isinstance(caller_context, EventTopLevelSolc):
direct_contracts = []
direct_functions_parser = []
scope = caller_context.underlying_event.file_scope
elif isinstance(caller_context, CustomErrorSolc): elif isinstance(caller_context, CustomErrorSolc):
if caller_context.contract_parser: if caller_context.contract_parser:
direct_contracts = [caller_context.contract_parser.underlying_contract] direct_contracts = [caller_context.contract_parser.underlying_contract]

@ -25,7 +25,7 @@ from slither.solc_parsing.declarations.caller_context import CallerContextExpres
from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.event import EventSolc from slither.solc_parsing.declarations.event_top_level import EventTopLevelSolc
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc
from slither.solc_parsing.exceptions import VariableNotFound from slither.solc_parsing.exceptions import VariableNotFound
@ -92,6 +92,7 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
self._variables_top_level_parser: List[TopLevelVariableSolc] = [] self._variables_top_level_parser: List[TopLevelVariableSolc] = []
self._functions_top_level_parser: List[FunctionSolc] = [] self._functions_top_level_parser: List[FunctionSolc] = []
self._using_for_top_level_parser: List[UsingForTopLevelSolc] = [] self._using_for_top_level_parser: List[UsingForTopLevelSolc] = []
self._events_top_level_parser: List[EventTopLevelSolc] = []
self._all_functions_and_modifier_parser: List[FunctionSolc] = [] self._all_functions_and_modifier_parser: List[FunctionSolc] = []
self._top_level_contracts_counter = 0 self._top_level_contracts_counter = 0
@ -364,9 +365,9 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
event = EventTopLevel(scope) event = EventTopLevel(scope)
event.set_offset(top_level_data["src"], self._compilation_unit) event.set_offset(top_level_data["src"], self._compilation_unit)
event_parser = EventSolc(event, top_level_data, self) # type: ignore event_parser = EventTopLevelSolc(event, top_level_data, self) # type: ignore
event_parser.analyze() # type: ignore self._events_top_level_parser.append(event_parser)
scope.events[event.full_name] = event scope.events.add(event)
self._compilation_unit.events_top_level.append(event) self._compilation_unit.events_top_level.append(event)
else: else:
@ -626,6 +627,7 @@ Please rename it, this name is reserved for Slither's internals"""
self._analyze_top_level_variables() self._analyze_top_level_variables()
self._analyze_top_level_structures() self._analyze_top_level_structures()
self._analyze_top_level_events()
# Start with the contracts without inheritance # Start with the contracts without inheritance
# Analyze a contract only if all its fathers # Analyze a contract only if all its fathers
@ -736,6 +738,13 @@ Please rename it, this name is reserved for Slither's internals"""
except (VariableNotFound, KeyError) as e: except (VariableNotFound, KeyError) as e:
raise SlitherException(f"Missing {e} during variable analyze") from e raise SlitherException(f"Missing {e} during variable analyze") from e
def _analyze_top_level_events(self) -> None:
try:
for event in self._events_top_level_parser:
event.analyze()
except (VariableNotFound, KeyError) as e:
raise SlitherException(f"Missing event {e} during top level event analyze") from e
def _analyze_params_top_level_function(self) -> None: def _analyze_params_top_level_function(self) -> None:
for func_parser in self._functions_top_level_parser: for func_parser in self._functions_top_level_parser:
func_parser.analyze_params() func_parser.analyze_params()

@ -232,6 +232,7 @@ def parse_type(
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc
from slither.solc_parsing.declarations.event_top_level import EventTopLevelSolc
sl: "SlitherCompilationUnit" sl: "SlitherCompilationUnit"
renaming: Dict[str, str] renaming: Dict[str, str]
@ -266,7 +267,13 @@ def parse_type(
functions = [] functions = []
elif isinstance( elif isinstance(
caller_context, caller_context,
(StructureTopLevelSolc, CustomErrorSolc, TopLevelVariableSolc, UsingForTopLevelSolc), (
StructureTopLevelSolc,
CustomErrorSolc,
TopLevelVariableSolc,
UsingForTopLevelSolc,
EventTopLevelSolc,
),
): ):
if isinstance(caller_context, StructureTopLevelSolc): if isinstance(caller_context, StructureTopLevelSolc):
scope = caller_context.underlying_structure.file_scope scope = caller_context.underlying_structure.file_scope
@ -274,6 +281,8 @@ def parse_type(
scope = caller_context.underlying_variable.file_scope scope = caller_context.underlying_variable.file_scope
elif isinstance(caller_context, UsingForTopLevelSolc): elif isinstance(caller_context, UsingForTopLevelSolc):
scope = caller_context.underlying_using_for.file_scope scope = caller_context.underlying_using_for.file_scope
elif isinstance(caller_context, EventTopLevelSolc):
scope = caller_context.underlying_event.file_scope
else: else:
assert isinstance(caller_context, CustomErrorSolc) assert isinstance(caller_context, CustomErrorSolc)
custom_error = caller_context.underlying_custom_error custom_error = caller_context.underlying_custom_error

@ -4,10 +4,10 @@ import logging
import os import os
import zipfile import zipfile
from collections import OrderedDict from collections import OrderedDict
from importlib import metadata
from typing import Tuple, Optional, Dict, List, Union, Any, TYPE_CHECKING, Type from typing import Tuple, Optional, Dict, List, Union, Any, TYPE_CHECKING, Type
from zipfile import ZipFile from zipfile import ZipFile
from pkg_resources import require
from slither.core.cfg.node import Node from slither.core.cfg.node import Node
from slither.core.declarations import ( from slither.core.declarations import (
@ -161,7 +161,7 @@ def output_to_sarif(
"driver": { "driver": {
"name": "Slither", "name": "Slither",
"informationUri": "https://github.com/crytic/slither", "informationUri": "https://github.com/crytic/slither",
"version": require("slither-analyzer")[0].version, "version": metadata.version("slither-analyzer"),
"rules": [], "rules": [],
} }
}, },

@ -153,10 +153,10 @@ def parse_expression(
parsed_expr.set_offset(expression.src, caller_context.compilation_unit) parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr return parsed_expr
# `raw_call` and `send` are treated specially in order to force `extract_tmp_call` to treat this as a `HighLevelCall` which will be converted
# to a `LowLevelCall` by `convert_to_low_level`. This is an artifact of the late conversion of Solidity...
if called.value.name == "raw_call()": if called.value.name == "raw_call()":
args = [parse_expression(a, caller_context) for a in expression.args] args = [parse_expression(a, caller_context) for a in expression.args]
# This is treated specially in order to force `extract_tmp_call` to treat this as a `HighLevelCall` which will be converted
# to a `LowLevelCall` by `convert_to_low_level`. This is an artifact of the late conversion of Solidity...
call = CallExpression( call = CallExpression(
MemberAccess("raw_call", "tuple(bool,bytes32)", args[0]), MemberAccess("raw_call", "tuple(bool,bytes32)", args[0]),
args[1:], args[1:],
@ -183,6 +183,17 @@ def parse_expression(
return call return call
if called.value.name == "send()":
args = [parse_expression(a, caller_context) for a in expression.args]
call = CallExpression(
MemberAccess("send", "tuple()", args[0]),
args[1:],
"tuple()",
)
call.set_offset(expression.src, caller_context.compilation_unit)
return call
if expression.args and isinstance(expression.args[0], VyDict): if expression.args and isinstance(expression.args[0], VyDict):
arguments = [] arguments = []
for val in expression.args[0].values: for val in expression.args[0].values:

@ -467,6 +467,8 @@ ALL_TESTS = [
), ),
Test("user_defined_operators-0.8.19.sol", ["0.8.19"]), Test("user_defined_operators-0.8.19.sol", ["0.8.19"]),
Test("aliasing/main.sol", ["0.8.19"]), Test("aliasing/main.sol", ["0.8.19"]),
Test("aliasing/alias-unit-NewContract.sol", ["0.8.19"]),
Test("aliasing/alias-symbol-NewContract.sol", ["0.8.19"]),
Test("type-aliases.sol", ["0.8.19"]), Test("type-aliases.sol", ["0.8.19"]),
Test("enum-max-min.sol", ["0.8.19"]), Test("enum-max-min.sol", ["0.8.19"]),
Test("event-top-level.sol", ["0.8.22"]), Test("event-top-level.sol", ["0.8.22"]),

@ -0,0 +1,8 @@
import {MyContract as MyAliasedContract} from "./MyContract.sol";
contract Test {
MyAliasedContract c;
constructor() {
c = new MyAliasedContract();
}
}

@ -0,0 +1,9 @@
import "./MyContract.sol" as MyAliasedContract;
contract Test {
MyAliasedContract.MyContract c;
constructor() {
c = new MyAliasedContract.MyContract();
}
}

@ -1,7 +1,17 @@
event MyEvent(uint256 a); event MyEvent(uint256 a);
uint256 constant A = 3;
event MyEvent2(uint8[A]);
contract T { contract T {
type MyType is uint256;
event MyCustomEvent(MyType mytype);
function a() public { function a() public {
emit MyEvent(2); emit MyEvent(2);
} }
function b() public {
emit MyEvent2([1,2,3]);
}
} }

@ -0,0 +1,6 @@
{
"MyContract": {},
"Test": {
"constructor()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,6 @@
{
"MyContract": {},
"Test": {
"constructor()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -1,5 +1,6 @@
{ {
"T": { "T": {
"a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" "a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n",
"b()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
} }
} }

@ -16,12 +16,12 @@ EXPRESSION:
user_shares.append(1) user_shares.append(1)
IRs: IRs:
REF_1 -> LENGTH user_shares REF_2 -> LENGTH user_shares
TMP_3(uint256) := REF_1(uint256) TMP_4(uint256) := REF_2(uint256)
TMP_4(uint256) = TMP_3 (c)+ 1 TMP_5(uint256) = TMP_4 (c)+ 1
REF_1(uint256) (->user_shares) := TMP_4(uint256) REF_2(uint256) (->user_shares) := TMP_5(uint256)
REF_2(uint256) -> user_shares[TMP_3] REF_3(uint256) -> user_shares[TMP_4]
REF_2(uint256) (->user_shares) := 1(uint256)"]; REF_3(uint256) (->user_shares) := 1(uint256)"];
2->3; 2->3;
3[label="Node Type: EXPRESSION 3 3[label="Node Type: EXPRESSION 3
@ -29,10 +29,10 @@ EXPRESSION:
user_shares.pop() user_shares.pop()
IRs: IRs:
REF_4 -> LENGTH user_shares REF_5 -> LENGTH user_shares
TMP_6(uint256) = REF_4 (c)- 1 TMP_7(uint256) = REF_5 (c)- 1
REF_5(uint256) -> user_shares[TMP_6] REF_6(uint256) -> user_shares[TMP_7]
REF_5 = delete REF_5 REF_6 = delete REF_6
REF_6 -> LENGTH user_shares REF_7 -> LENGTH user_shares
REF_6(uint256) (->user_shares) := TMP_6(uint256)"]; REF_7(uint256) (->user_shares) := TMP_7(uint256)"];
} }

@ -115,4 +115,12 @@ x = self.balance
IRs: IRs:
x(uint256) := self.balance(uint256)"]; x(uint256) := self.balance(uint256)"];
14->15;
15[label="Node Type: EXPRESSION 15
EXPRESSION:
msg.sender.send(x)
IRs:
TMP_2 = SEND dest:msg.sender value:x"];
} }

@ -16,6 +16,7 @@ def test_builtins():
m: address = tx.origin m: address = tx.origin
n: uint256 = tx.gasprice n: uint256 = tx.gasprice
x: uint256 = self.balance x: uint256 = self.balance
send(msg.sender, x)
@external @external
def c(x: uint256): def c(x: uint256):

@ -10,7 +10,8 @@ from slither.slithir.operations import (
) )
def test_with_explicit_return(slither_from_solidity_source) -> None: @pytest.mark.parametrize("legacy", [True, False])
def test_with_explicit_return(slither_from_solidity_source, legacy) -> None:
source = """ source = """
contract Contract { contract Contract {
function foo(int x) public returns (int y) { function foo(int x) public returns (int y) {
@ -22,31 +23,31 @@ def test_with_explicit_return(slither_from_solidity_source) -> None:
} }
} }
""" """
for legacy in [True, False]: with slither_from_solidity_source(source, legacy=legacy) as slither:
with slither_from_solidity_source(source, legacy=legacy) as slither: c: Contract = slither.get_contract_from_name("Contract")[0]
c: Contract = slither.get_contract_from_name("Contract")[0] f: Function = c.functions[0]
f: Function = c.functions[0] node_if: Node = f.nodes[1]
node_if: Node = f.nodes[1] node_true = node_if.son_true
node_true = node_if.son_true node_false = node_if.son_false
node_false = node_if.son_false assert node_true.type == NodeType.RETURN
assert node_true.type == NodeType.RETURN assert isinstance(node_true.irs[0], Return)
assert isinstance(node_true.irs[0], Return) assert node_true.irs[0].values[0] == f.get_local_variable_from_name("x")
assert node_true.irs[0].values[0] == f.get_local_variable_from_name("x") assert len(node_true.sons) == 0
assert len(node_true.sons) == 0 node_end_if = node_false.sons[0]
node_end_if = node_false.sons[0] assert node_end_if.type == NodeType.ENDIF
assert node_end_if.type == NodeType.ENDIF assert node_end_if.sons[0].type == NodeType.RETURN
assert node_end_if.sons[0].type == NodeType.RETURN node_ret = node_end_if.sons[0]
node_ret = node_end_if.sons[0] assert isinstance(node_ret.irs[0], Return)
assert isinstance(node_ret.irs[0], Return) assert node_ret.irs[0].values[0] == f.get_local_variable_from_name("y")
assert node_ret.irs[0].values[0] == f.get_local_variable_from_name("y")
def test_return_multiple_with_struct(slither_from_solidity_source) -> None: @pytest.mark.parametrize("legacy", [True, False])
def test_return_multiple_with_struct(slither_from_solidity_source, legacy) -> None:
source = """ source = """
struct St { struct St {
uint256 value; uint256 value;
} }
contract Contract { contract Contract {
function foo(St memory x) public returns (St memory y, uint256 z) { function foo(St memory x) public returns (St memory y, uint256 z) {
z = x.value; z = x.value;
@ -54,16 +55,15 @@ def test_return_multiple_with_struct(slither_from_solidity_source) -> None:
} }
} }
""" """
for legacy in [True, False]: with slither_from_solidity_source(source, legacy=legacy) as slither:
with slither_from_solidity_source(source, legacy=legacy) as slither: c: Contract = slither.get_contract_from_name("Contract")[0]
c: Contract = slither.get_contract_from_name("Contract")[0] f: Function = c.functions[0]
f: Function = c.functions[0] assert len(f.nodes) == 4
assert len(f.nodes) == 4 node = f.nodes[3]
node = f.nodes[3] assert node.type == NodeType.RETURN
assert node.type == NodeType.RETURN assert isinstance(node.irs[0], Return)
assert isinstance(node.irs[0], Return) assert node.irs[0].values[0] == f.get_local_variable_from_name("y")
assert node.irs[0].values[0] == f.get_local_variable_from_name("y") assert node.irs[0].values[1] == f.get_local_variable_from_name("z")
assert node.irs[0].values[1] == f.get_local_variable_from_name("z")
def test_nested_ifs_with_loop_legacy(slither_from_solidity_source) -> None: def test_nested_ifs_with_loop_legacy(slither_from_solidity_source) -> None:
@ -149,7 +149,8 @@ def test_nested_ifs_with_loop_compact(slither_from_solidity_source) -> None:
@pytest.mark.xfail # Explicit returns inside assembly are currently not parsed as return nodes @pytest.mark.xfail # Explicit returns inside assembly are currently not parsed as return nodes
def test_assembly_switch_cases(slither_from_solidity_source): @pytest.mark.parametrize("legacy", [True, False])
def test_assembly_switch_cases(slither_from_solidity_source, legacy):
source = """ source = """
contract Contract { contract Contract {
function foo(uint a) public returns (uint x) { function foo(uint a) public returns (uint x) {
@ -164,28 +165,28 @@ def test_assembly_switch_cases(slither_from_solidity_source):
} }
} }
""" """
for legacy in [True, False]: with slither_from_solidity_source(source, solc_version="0.8.0", legacy=legacy) as slither:
with slither_from_solidity_source(source, solc_version="0.8.0", legacy=legacy) as slither: c: Contract = slither.get_contract_from_name("Contract")[0]
c: Contract = slither.get_contract_from_name("Contract")[0] f = c.functions[0]
f = c.functions[0] if legacy:
if legacy: node = f.nodes[2]
node = f.nodes[2] assert node.type == NodeType.RETURN
assert node.type == NodeType.RETURN assert isinstance(node.irs[0], Return)
assert isinstance(node.irs[0], Return) assert node.irs[0].values[0] == f.get_local_variable_from_name("x")
assert node.irs[0].values[0] == f.get_local_variable_from_name("x") else:
else: node_end_if = f.nodes[5]
node_end_if = f.nodes[5] assert node_end_if.sons[0].type == NodeType.RETURN
assert node_end_if.sons[0].type == NodeType.RETURN node_implicit = node_end_if.sons[0]
node_implicit = node_end_if.sons[0] assert isinstance(node_implicit.irs[0], Return)
assert isinstance(node_implicit.irs[0], Return) assert node_implicit.irs[0].values[0] == f.get_local_variable_from_name("x")
assert node_implicit.irs[0].values[0] == f.get_local_variable_from_name("x") # This part will fail until issue #1927 is fixed
# This part will fail until issue #1927 is fixed node_explicit = f.nodes[10]
node_explicit = f.nodes[10] assert node_explicit.type == NodeType.RETURN
assert node_explicit.type == NodeType.RETURN assert len(node_explicit.sons) == 0
assert len(node_explicit.sons) == 0
def test_issue_1846_ternary_in_ternary(slither_from_solidity_source): @pytest.mark.parametrize("legacy", [True, False])
def test_issue_1846_ternary_in_ternary(slither_from_solidity_source, legacy):
source = """ source = """
contract Contract { contract Contract {
function foo(uint x) public returns (uint y) { function foo(uint x) public returns (uint y) {
@ -193,14 +194,13 @@ def test_issue_1846_ternary_in_ternary(slither_from_solidity_source):
} }
} }
""" """
for legacy in [True, False]: with slither_from_solidity_source(source, legacy=legacy) as slither:
with slither_from_solidity_source(source, legacy=legacy) as slither: c: Contract = slither.get_contract_from_name("Contract")[0]
c: Contract = slither.get_contract_from_name("Contract")[0] f = c.functions[0]
f = c.functions[0] node_end_if = f.nodes[3]
node_end_if = f.nodes[3] assert node_end_if.type == NodeType.ENDIF
assert node_end_if.type == NodeType.ENDIF assert len(node_end_if.sons) == 1
assert len(node_end_if.sons) == 1 node_ret = node_end_if.sons[0]
node_ret = node_end_if.sons[0] assert node_ret.type == NodeType.RETURN
assert node_ret.type == NodeType.RETURN assert isinstance(node_ret.irs[0], Return)
assert isinstance(node_ret.irs[0], Return) assert node_ret.irs[0].values[0] == f.get_local_variable_from_name("y")
assert node_ret.irs[0].values[0] == f.get_local_variable_from_name("y")

Loading…
Cancel
Save