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
92 changes: 92 additions & 0 deletions slither/core/declarations/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,7 @@ def add_constructor_variables(self) -> None:
next_node.add_father(prev_node)
prev_node = next_node
counter += 1
self._rewrite_ternary_in_constructor_variables(constructor_variable, counter)
break

for idx, variable_candidate in enumerate(self.state_variables):
Expand Down Expand Up @@ -1550,6 +1551,97 @@ def _create_node(
node.add_expression(expression)
return node

@staticmethod
def _rewrite_ternary_in_constructor_variables(func: Function, counter: int) -> None:
"""Rewrite ternary expressions in constructor variable initialization nodes.

State variable initializers are processed outside the normal function parsing
pipeline, so ternary expressions are not rewritten by _rewrite_ternary_as_if_else.
This method performs the equivalent transformation for constructor variable nodes.
"""
from slither.core.cfg.node import Node, NodeType
from slither.visitors.expression.has_conditional import HasConditional
from slither.utils.expression_manipulations import SplitTernaryExpression

ternary_found = True
while ternary_found:
ternary_found = False
for node in list(func.nodes):
if not node.expression:
continue

has_cond = HasConditional(node.expression)
if not has_cond.result():
continue

st = SplitTernaryExpression(node.expression)
condition = st.condition
if not condition:
continue

true_expr = st.true_expression
false_expr = st.false_expression

# Create IF node
condition_node = Node(NodeType.IF, counter, node.scope, func.file_scope)
counter += 1
condition_node.set_offset(node.source_mapping, func.compilation_unit)
condition_node.set_function(func)
condition_node.add_expression(condition)

# Create true branch
true_node = Node(NodeType.EXPRESSION, counter, node.scope, func.file_scope)
counter += 1
true_node.set_offset(node.source_mapping, func.compilation_unit)
true_node.set_function(func)
true_node.add_expression(true_expr)

# Create false branch
false_node = Node(NodeType.EXPRESSION, counter, node.scope, func.file_scope)
counter += 1
false_node.set_offset(node.source_mapping, func.compilation_unit)
false_node.set_function(func)
false_node.add_expression(false_expr)

# Create ENDIF
endif_node = Node(NodeType.ENDIF, counter, node.scope, func.file_scope)
counter += 1
endif_node.set_offset(node.source_mapping, func.compilation_unit)
endif_node.set_function(func)

# Rewire CFG: connect fathers to condition node
for father in node.fathers:
father.replace_son(node, condition_node)
condition_node.add_father(father)

# Rewire CFG: connect sons to endif node
for son in node.sons:
son.remove_father(node)
son.add_father(endif_node)
endif_node.add_son(son)

# Wire IF -> true/false branches -> ENDIF
condition_node.add_son(true_node)
true_node.add_father(condition_node)
condition_node.add_son(false_node)
false_node.add_father(condition_node)
true_node.add_son(endif_node)
endif_node.add_father(true_node)
false_node.add_son(endif_node)
endif_node.add_father(false_node)

# Update entry point if the rewritten node was the entry
if func.entry_point == node:
func.entry_point = condition_node

# Replace old node with new nodes
new_nodes = [n for n in func.nodes if n.node_id != node.node_id]
new_nodes.extend([condition_node, true_node, false_node, endif_node])
func.nodes = new_nodes

ternary_found = True
break

# endregion
###################################################################################
###################################################################################
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract A {
struct S {
uint32 user;
}
}

// Ternary expression in state variable initializer with struct member access.
// Before the fix, this caused:
// SlithIRError: Ternary operator are not convertible to SlithIR
// because constructor variable nodes were not processed by ternary rewriting.
contract B {
A.S a;
A.S b;
uint32 result = (true ? a.user : b.user);
}
33 changes: 33 additions & 0 deletions tests/unit/core/test_ternary_state_var.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Test that ternary expressions in state variable initializers are handled correctly.

Regression test for https://github.com/crytic/slither/issues/2836
"""

from pathlib import Path

from crytic_compile import CryticCompile
from crytic_compile.platform.solc_standard_json import SolcStandardJson

from slither import Slither

TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" / "ternary_in_state_var"


def test_ternary_in_state_variable_initializer(solc_binary_path) -> None:
"""Ensure ternary expressions in state variable initializers don't crash.

Before the fix, a ConditionalExpression in a state variable initializer
would reach SlithIR conversion without being split, causing SlithIRError.
"""
solc_path = solc_binary_path("0.8.0")
standard_json = SolcStandardJson()
for source_file in TEST_DATA_DIR.rglob("**/*.sol"):
standard_json.add_source_file(Path(source_file).as_posix())
compilation = CryticCompile(standard_json, solc=solc_path)
sl = Slither(compilation)

# Verify contract B was parsed successfully
contract_b = next(c for c in sl.contracts if c.name == "B")
result_var = next(v for v in contract_b.state_variables if v.name == "result")
assert result_var is not None
assert "uint32" in str(result_var.type)