Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions slither/solc_parsing/cfg/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ def analyze_expressions(self, caller_context: Union["FunctionSolc", "ModifierSol
find_call = FindCalls(expression)
self._node.calls_as_expression = find_call.result()

# Classify calls into internal and external
# Internal: direct calls, Solidity built-ins (abi.encode, etc.), library calls
# External: calls to external contracts
# Classify calls into internal and external.
# Note: using-for library calls are re-classified later in
# _analyze_using_for once the directives are available.
internal, external = classify_calls(self._node.calls_as_expression)
self._node.internal_calls_as_expressions = internal
self._node.external_calls_as_expressions = external
21 changes: 21 additions & 0 deletions slither/solc_parsing/slither_compilation_unit_solc.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,8 @@ def _analyze_third_part(
def _analyze_using_for(
self, contracts_to_be_analyzed: list[ContractSolc], libraries: list[ContractSolc]
) -> None:
from slither.visitors.expression.call_classification import classify_calls

self._analyze_top_level_using_for()

for lib in libraries:
Expand All @@ -721,6 +723,25 @@ def _analyze_using_for(
else:
contracts_to_be_analyzed += [contract]

# Re-classify calls now that using-for directives are available.
# The initial classification in NodeSolc.analyze_expressions runs
# before using-for is parsed, so library calls via ``using L for T``
# are incorrectly marked external. Re-running with the complete
# using-for map fixes this.
for contract in self._underlying_contract_to_parser:
using_for = contract.using_for_complete
if not using_for:
continue
for func in contract.functions_declared + contract.modifiers_declared:
for node in func.nodes:
if not node.calls_as_expression:
continue
internal, external = classify_calls(
node.calls_as_expression, using_for=using_for
)
node.internal_calls_as_expressions = internal
node.external_calls_as_expressions = external

def _analyze_enums(self, contract: ContractSolc) -> None:
# Enum must be analyzed first
contract.analyze_enums()
Expand Down
71 changes: 65 additions & 6 deletions slither/visitors/expression/call_classification.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,62 @@
from __future__ import annotations

from collections.abc import Callable
from typing import TYPE_CHECKING

from slither.core.declarations import Contract
from slither.core.declarations.solidity_variables import SolidityVariable
from slither.core.expressions.call_expression import CallExpression
from slither.core.expressions.identifier import Identifier
from slither.core.expressions.member_access import MemberAccess
from slither.core.variables.variable import Variable

if TYPE_CHECKING:
from slither.utils.using_for import USING_FOR

ExternalIdentifierPredicate = Callable[[Identifier], bool]


def _is_using_for_library_call(
variable: Variable,
member_name: str,
using_for: USING_FOR,
) -> bool:
"""Return True when *member_name* resolves to a library function
attached to *variable*'s type via a ``using … for`` directive.
"""
from slither.core.solidity_types.user_defined_type import (
UserDefinedType,
)

var_type = variable.type
if var_type is None:
return False

# Collect candidate library entries for the variable's type.
# Keys can be Type objects, the "*" wildcard, or string representations.
candidates = []
for key, items in using_for.items():
if key == "*" or key == var_type or str(key) == str(var_type):
candidates.extend(items)

for item in candidates:
# ``using LibContract for SomeType`` → item is UserDefinedType
if isinstance(item, UserDefinedType):
lib = item.type
if isinstance(lib, Contract) and lib.is_library:
if any(f.name == member_name for f in lib.functions):
return True
# ``using {freeFunc} for SomeType`` → item is Function
if hasattr(item, "name") and item.name == member_name:
return True

return False


def classify_calls(
calls: list[CallExpression],
is_external_identifier: ExternalIdentifierPredicate | None = None,
using_for: USING_FOR | None = None,
) -> tuple[list[CallExpression], list[CallExpression]]:
"""
Classify call expressions into internal and external calls.
Expand All @@ -22,11 +65,14 @@ def classify_calls(
Internal calls include:
- Direct function calls (e.g., myFunc())
- Solidity built-in calls (e.g., abi.encode(), abi.decode())
- Library calls (e.g., SafeMath.add())
- Library calls (e.g., SafeMath.add(), or addr.sendValue() via using-for)

Args:
calls: List of CallExpression to classify
is_external_identifier: Optional predicate to mark Identifier calls as external
is_external_identifier: Optional predicate to mark Identifier calls
as external
using_for: Optional mapping from ``contract.using_for_complete``
used to recognise ``using Library for Type`` calls as internal

Returns:
Tuple of (internal_calls, external_calls)
Expand All @@ -51,19 +97,32 @@ def classify_calls(
base_value = base_expr.value

# Solidity built-ins (abi, msg, block, tx, etc.)
# Note: "this" is a SolidityVariable but this.foo() is an external call
if isinstance(base_value, SolidityVariable) and base_value.name != "this":
# Note: "this" is a SolidityVariable but this.foo() is
# an external call
if (
isinstance(base_value, SolidityVariable)
and base_value.name != "this"
):
internal_calls.append(call)
# Library calls
# Library calls (e.g., SafeMath.add())
elif isinstance(base_value, Contract) and base_value.is_library:
internal_calls.append(call)
# using-for library calls (e.g., addr.sendValue())
elif (
using_for
and isinstance(base_value, Variable)
and _is_using_for_library_call(
base_value, called.member_name, using_for
)
):
internal_calls.append(call)
else:
external_calls.append(call)
else:
external_calls.append(call)
continue

# Other cases (e.g., complex expressions) - treat as external to be safe
# Other cases (e.g., complex expressions) treat as external
external_calls.append(call)

return internal_calls, external_calls
Loading