slither.utils.expression_manipulations

We use protected member, to avoid having setter in the expression as they should be immutable

  1"""
  2    We use protected member, to avoid having setter in the expression
  3    as they should be immutable
  4"""
  5import copy
  6from typing import Union, Callable
  7
  8from slither.all_exceptions import SlitherException
  9from slither.core.expressions import UnaryOperation
 10from slither.core.expressions.assignment_operation import AssignmentOperation
 11from slither.core.expressions.binary_operation import BinaryOperation
 12from slither.core.expressions.call_expression import CallExpression
 13from slither.core.expressions.conditional_expression import ConditionalExpression
 14from slither.core.expressions.elementary_type_name_expression import ElementaryTypeNameExpression
 15from slither.core.expressions.expression import Expression
 16from slither.core.expressions.identifier import Identifier
 17from slither.core.expressions.index_access import IndexAccess
 18from slither.core.expressions.literal import Literal
 19from slither.core.expressions.member_access import MemberAccess
 20from slither.core.expressions.new_array import NewArray
 21from slither.core.expressions.new_contract import NewContract
 22from slither.core.expressions.tuple_expression import TupleExpression
 23from slither.core.expressions.type_conversion import TypeConversion
 24from slither.core.expressions.new_elementary_type import NewElementaryType
 25
 26# pylint: disable=protected-access
 27def f_expressions(
 28    e: Union[AssignmentOperation, BinaryOperation, TupleExpression],
 29    x: Union[Identifier, Literal, MemberAccess, IndexAccess],
 30) -> None:
 31    e._expressions.append(x)
 32
 33
 34def f_call(e: CallExpression, x: ElementaryTypeNameExpression) -> None:
 35    e._arguments.append(x)
 36
 37
 38def f_call_value(e: CallExpression, x):
 39    e._value = x
 40
 41
 42def f_call_gas(e: CallExpression, x):
 43    e._gas = x
 44
 45
 46def f_expression(e: Union[TypeConversion, UnaryOperation, MemberAccess], x: CallExpression) -> None:
 47    e._expression = x
 48
 49
 50def f_called(e: CallExpression, x: Identifier) -> None:
 51    e._called = x
 52
 53
 54class SplitTernaryExpression:
 55    def __init__(self, expression: Union[AssignmentOperation, ConditionalExpression]) -> None:
 56
 57        if isinstance(expression, ConditionalExpression):
 58            self.true_expression = copy.copy(expression.then_expression)
 59            self.false_expression = copy.copy(expression.else_expression)
 60            self.condition = copy.copy(expression.if_expression)
 61        else:
 62            self.true_expression = copy.copy(expression)
 63            self.false_expression = copy.copy(expression)
 64            self.condition = None
 65            self.copy_expression(expression, self.true_expression, self.false_expression)
 66
 67    def conditional_not_ahead(
 68        self,
 69        next_expr: Expression,
 70        true_expression: Union[AssignmentOperation, MemberAccess],
 71        false_expression: Union[AssignmentOperation, MemberAccess],
 72        f: Callable,
 73    ) -> bool:
 74        # look ahead for parenthetical expression (.. ? .. : ..)
 75        if (
 76            isinstance(next_expr, TupleExpression)
 77            and len(next_expr.expressions) == 1
 78            and isinstance(next_expr.expressions[0], ConditionalExpression)
 79        ):
 80            next_expr = next_expr.expressions[0]
 81
 82        if isinstance(next_expr, ConditionalExpression):
 83            f(true_expression, copy.copy(next_expr.then_expression))
 84            f(false_expression, copy.copy(next_expr.else_expression))
 85            self.condition = copy.copy(next_expr.if_expression)
 86            return False
 87
 88        f(true_expression, copy.copy(next_expr))
 89        f(false_expression, copy.copy(next_expr))
 90        return True
 91
 92    def copy_expression(
 93        self, expression: Expression, true_expression: Expression, false_expression: Expression
 94    ) -> None:
 95        if self.condition:
 96            return
 97
 98        if isinstance(expression, ConditionalExpression):
 99            raise SlitherException("Nested ternary operator not handled")
100
101        if isinstance(
102            expression,
103            (
104                Literal,
105                Identifier,
106                NewArray,
107                NewContract,
108                ElementaryTypeNameExpression,
109                NewElementaryType,
110            ),
111        ):
112            return
113
114        if isinstance(
115            expression, (AssignmentOperation, BinaryOperation, TupleExpression, IndexAccess)
116        ):
117            true_expression._expressions = []
118            false_expression._expressions = []
119            self.convert_expressions(expression, true_expression, false_expression)
120
121        elif isinstance(expression, CallExpression):
122            next_expr = expression.called
123            self.convert_call_expression(expression, next_expr, true_expression, false_expression)
124
125        elif isinstance(expression, (TypeConversion, UnaryOperation, MemberAccess)):
126            next_expr = expression.expression
127            if self.conditional_not_ahead(
128                next_expr, true_expression, false_expression, f_expression
129            ):
130                self.copy_expression(
131                    expression.expression,
132                    true_expression.expression,
133                    false_expression.expression,
134                )
135
136        else:
137            raise SlitherException(
138                f"Ternary operation not handled {expression}({type(expression)})"
139            )
140
141    def convert_expressions(
142        self,
143        expression: Union[AssignmentOperation, BinaryOperation, TupleExpression],
144        true_expression: Expression,
145        false_expression: Expression,
146    ) -> None:
147        for next_expr in expression.expressions:
148            # TODO: can we get rid of `NoneType` expressions in `TupleExpression`?
149            # montyly: this might happen with unnamed tuple (ex: (,,,) = f()), but it needs to be checked
150            if next_expr is not None:
151
152                if self.conditional_not_ahead(
153                    next_expr, true_expression, false_expression, f_expressions
154                ):
155                    # always on last arguments added
156                    self.copy_expression(
157                        next_expr,
158                        true_expression.expressions[-1],
159                        false_expression.expressions[-1],
160                    )
161            else:
162                true_expression.expressions.append(None)
163                false_expression.expressions.append(None)
164
165    def convert_index_access(
166        self, next_expr: IndexAccess, true_expression: Expression, false_expression: Expression
167    ) -> None:
168        # create an index access for each branch
169        #  x[if cond ? 1 : 2] -> if cond { x[1] } else { x[2] }
170        for expr in next_expr.expressions:
171            if self.conditional_not_ahead(expr, true_expression, false_expression, f_expressions):
172                self.copy_expression(
173                    expr,
174                    true_expression.expressions[-1],
175                    false_expression.expressions[-1],
176                )
177
178    def convert_call_expression(
179        self,
180        expression: CallExpression,
181        next_expr: Expression,
182        true_expression: Expression,
183        false_expression: Expression,
184    ) -> None:
185        # case of lib
186        # (.. ? .. : ..).add
187        if self.conditional_not_ahead(next_expr, true_expression, false_expression, f_called):
188            self.copy_expression(next_expr, true_expression.called, false_expression.called)
189
190        # In order to handle ternaries in both call options, gas and value, we return early if the
191        # conditional is not ahead to rewrite both ternaries (see `_rewrite_ternary_as_if_else`).
192        if expression.call_gas:
193            # case of (..).func{gas: .. ? .. : ..}()
194            next_expr = expression.call_gas
195            if self.conditional_not_ahead(next_expr, true_expression, false_expression, f_call_gas):
196                self.copy_expression(
197                    next_expr,
198                    true_expression.call_gas,
199                    false_expression.call_gas,
200                )
201            else:
202                return
203
204        if expression.call_value:
205            # case of (..).func{value: .. ? .. : ..}()
206            next_expr = expression.call_value
207            if self.conditional_not_ahead(
208                next_expr, true_expression, false_expression, f_call_value
209            ):
210                self.copy_expression(
211                    next_expr,
212                    true_expression.call_value,
213                    false_expression.call_value,
214                )
215            else:
216                return
217
218        true_expression._arguments = []
219        false_expression._arguments = []
220
221        for expr in expression.arguments:
222            if self.conditional_not_ahead(expr, true_expression, false_expression, f_call):
223                # always on last arguments added
224                self.copy_expression(
225                    expr,
226                    true_expression.arguments[-1],
227                    false_expression.arguments[-1],
228                )
28def f_expressions(
29    e: Union[AssignmentOperation, BinaryOperation, TupleExpression],
30    x: Union[Identifier, Literal, MemberAccess, IndexAccess],
31) -> None:
32    e._expressions.append(x)
35def f_call(e: CallExpression, x: ElementaryTypeNameExpression) -> None:
36    e._arguments.append(x)
def f_call_value(e: slither.core.expressions.call_expression.CallExpression, x):
39def f_call_value(e: CallExpression, x):
40    e._value = x
def f_call_gas(e: slither.core.expressions.call_expression.CallExpression, x):
43def f_call_gas(e: CallExpression, x):
44    e._gas = x
47def f_expression(e: Union[TypeConversion, UnaryOperation, MemberAccess], x: CallExpression) -> None:
48    e._expression = x
51def f_called(e: CallExpression, x: Identifier) -> None:
52    e._called = x
class SplitTernaryExpression:
 55class SplitTernaryExpression:
 56    def __init__(self, expression: Union[AssignmentOperation, ConditionalExpression]) -> None:
 57
 58        if isinstance(expression, ConditionalExpression):
 59            self.true_expression = copy.copy(expression.then_expression)
 60            self.false_expression = copy.copy(expression.else_expression)
 61            self.condition = copy.copy(expression.if_expression)
 62        else:
 63            self.true_expression = copy.copy(expression)
 64            self.false_expression = copy.copy(expression)
 65            self.condition = None
 66            self.copy_expression(expression, self.true_expression, self.false_expression)
 67
 68    def conditional_not_ahead(
 69        self,
 70        next_expr: Expression,
 71        true_expression: Union[AssignmentOperation, MemberAccess],
 72        false_expression: Union[AssignmentOperation, MemberAccess],
 73        f: Callable,
 74    ) -> bool:
 75        # look ahead for parenthetical expression (.. ? .. : ..)
 76        if (
 77            isinstance(next_expr, TupleExpression)
 78            and len(next_expr.expressions) == 1
 79            and isinstance(next_expr.expressions[0], ConditionalExpression)
 80        ):
 81            next_expr = next_expr.expressions[0]
 82
 83        if isinstance(next_expr, ConditionalExpression):
 84            f(true_expression, copy.copy(next_expr.then_expression))
 85            f(false_expression, copy.copy(next_expr.else_expression))
 86            self.condition = copy.copy(next_expr.if_expression)
 87            return False
 88
 89        f(true_expression, copy.copy(next_expr))
 90        f(false_expression, copy.copy(next_expr))
 91        return True
 92
 93    def copy_expression(
 94        self, expression: Expression, true_expression: Expression, false_expression: Expression
 95    ) -> None:
 96        if self.condition:
 97            return
 98
 99        if isinstance(expression, ConditionalExpression):
100            raise SlitherException("Nested ternary operator not handled")
101
102        if isinstance(
103            expression,
104            (
105                Literal,
106                Identifier,
107                NewArray,
108                NewContract,
109                ElementaryTypeNameExpression,
110                NewElementaryType,
111            ),
112        ):
113            return
114
115        if isinstance(
116            expression, (AssignmentOperation, BinaryOperation, TupleExpression, IndexAccess)
117        ):
118            true_expression._expressions = []
119            false_expression._expressions = []
120            self.convert_expressions(expression, true_expression, false_expression)
121
122        elif isinstance(expression, CallExpression):
123            next_expr = expression.called
124            self.convert_call_expression(expression, next_expr, true_expression, false_expression)
125
126        elif isinstance(expression, (TypeConversion, UnaryOperation, MemberAccess)):
127            next_expr = expression.expression
128            if self.conditional_not_ahead(
129                next_expr, true_expression, false_expression, f_expression
130            ):
131                self.copy_expression(
132                    expression.expression,
133                    true_expression.expression,
134                    false_expression.expression,
135                )
136
137        else:
138            raise SlitherException(
139                f"Ternary operation not handled {expression}({type(expression)})"
140            )
141
142    def convert_expressions(
143        self,
144        expression: Union[AssignmentOperation, BinaryOperation, TupleExpression],
145        true_expression: Expression,
146        false_expression: Expression,
147    ) -> None:
148        for next_expr in expression.expressions:
149            # TODO: can we get rid of `NoneType` expressions in `TupleExpression`?
150            # montyly: this might happen with unnamed tuple (ex: (,,,) = f()), but it needs to be checked
151            if next_expr is not None:
152
153                if self.conditional_not_ahead(
154                    next_expr, true_expression, false_expression, f_expressions
155                ):
156                    # always on last arguments added
157                    self.copy_expression(
158                        next_expr,
159                        true_expression.expressions[-1],
160                        false_expression.expressions[-1],
161                    )
162            else:
163                true_expression.expressions.append(None)
164                false_expression.expressions.append(None)
165
166    def convert_index_access(
167        self, next_expr: IndexAccess, true_expression: Expression, false_expression: Expression
168    ) -> None:
169        # create an index access for each branch
170        #  x[if cond ? 1 : 2] -> if cond { x[1] } else { x[2] }
171        for expr in next_expr.expressions:
172            if self.conditional_not_ahead(expr, true_expression, false_expression, f_expressions):
173                self.copy_expression(
174                    expr,
175                    true_expression.expressions[-1],
176                    false_expression.expressions[-1],
177                )
178
179    def convert_call_expression(
180        self,
181        expression: CallExpression,
182        next_expr: Expression,
183        true_expression: Expression,
184        false_expression: Expression,
185    ) -> None:
186        # case of lib
187        # (.. ? .. : ..).add
188        if self.conditional_not_ahead(next_expr, true_expression, false_expression, f_called):
189            self.copy_expression(next_expr, true_expression.called, false_expression.called)
190
191        # In order to handle ternaries in both call options, gas and value, we return early if the
192        # conditional is not ahead to rewrite both ternaries (see `_rewrite_ternary_as_if_else`).
193        if expression.call_gas:
194            # case of (..).func{gas: .. ? .. : ..}()
195            next_expr = expression.call_gas
196            if self.conditional_not_ahead(next_expr, true_expression, false_expression, f_call_gas):
197                self.copy_expression(
198                    next_expr,
199                    true_expression.call_gas,
200                    false_expression.call_gas,
201                )
202            else:
203                return
204
205        if expression.call_value:
206            # case of (..).func{value: .. ? .. : ..}()
207            next_expr = expression.call_value
208            if self.conditional_not_ahead(
209                next_expr, true_expression, false_expression, f_call_value
210            ):
211                self.copy_expression(
212                    next_expr,
213                    true_expression.call_value,
214                    false_expression.call_value,
215                )
216            else:
217                return
218
219        true_expression._arguments = []
220        false_expression._arguments = []
221
222        for expr in expression.arguments:
223            if self.conditional_not_ahead(expr, true_expression, false_expression, f_call):
224                # always on last arguments added
225                self.copy_expression(
226                    expr,
227                    true_expression.arguments[-1],
228                    false_expression.arguments[-1],
229                )
56    def __init__(self, expression: Union[AssignmentOperation, ConditionalExpression]) -> None:
57
58        if isinstance(expression, ConditionalExpression):
59            self.true_expression = copy.copy(expression.then_expression)
60            self.false_expression = copy.copy(expression.else_expression)
61            self.condition = copy.copy(expression.if_expression)
62        else:
63            self.true_expression = copy.copy(expression)
64            self.false_expression = copy.copy(expression)
65            self.condition = None
66            self.copy_expression(expression, self.true_expression, self.false_expression)
68    def conditional_not_ahead(
69        self,
70        next_expr: Expression,
71        true_expression: Union[AssignmentOperation, MemberAccess],
72        false_expression: Union[AssignmentOperation, MemberAccess],
73        f: Callable,
74    ) -> bool:
75        # look ahead for parenthetical expression (.. ? .. : ..)
76        if (
77            isinstance(next_expr, TupleExpression)
78            and len(next_expr.expressions) == 1
79            and isinstance(next_expr.expressions[0], ConditionalExpression)
80        ):
81            next_expr = next_expr.expressions[0]
82
83        if isinstance(next_expr, ConditionalExpression):
84            f(true_expression, copy.copy(next_expr.then_expression))
85            f(false_expression, copy.copy(next_expr.else_expression))
86            self.condition = copy.copy(next_expr.if_expression)
87            return False
88
89        f(true_expression, copy.copy(next_expr))
90        f(false_expression, copy.copy(next_expr))
91        return True
def copy_expression( self, expression: slither.core.expressions.expression.Expression, true_expression: slither.core.expressions.expression.Expression, false_expression: slither.core.expressions.expression.Expression) -> None:
 93    def copy_expression(
 94        self, expression: Expression, true_expression: Expression, false_expression: Expression
 95    ) -> None:
 96        if self.condition:
 97            return
 98
 99        if isinstance(expression, ConditionalExpression):
100            raise SlitherException("Nested ternary operator not handled")
101
102        if isinstance(
103            expression,
104            (
105                Literal,
106                Identifier,
107                NewArray,
108                NewContract,
109                ElementaryTypeNameExpression,
110                NewElementaryType,
111            ),
112        ):
113            return
114
115        if isinstance(
116            expression, (AssignmentOperation, BinaryOperation, TupleExpression, IndexAccess)
117        ):
118            true_expression._expressions = []
119            false_expression._expressions = []
120            self.convert_expressions(expression, true_expression, false_expression)
121
122        elif isinstance(expression, CallExpression):
123            next_expr = expression.called
124            self.convert_call_expression(expression, next_expr, true_expression, false_expression)
125
126        elif isinstance(expression, (TypeConversion, UnaryOperation, MemberAccess)):
127            next_expr = expression.expression
128            if self.conditional_not_ahead(
129                next_expr, true_expression, false_expression, f_expression
130            ):
131                self.copy_expression(
132                    expression.expression,
133                    true_expression.expression,
134                    false_expression.expression,
135                )
136
137        else:
138            raise SlitherException(
139                f"Ternary operation not handled {expression}({type(expression)})"
140            )
142    def convert_expressions(
143        self,
144        expression: Union[AssignmentOperation, BinaryOperation, TupleExpression],
145        true_expression: Expression,
146        false_expression: Expression,
147    ) -> None:
148        for next_expr in expression.expressions:
149            # TODO: can we get rid of `NoneType` expressions in `TupleExpression`?
150            # montyly: this might happen with unnamed tuple (ex: (,,,) = f()), but it needs to be checked
151            if next_expr is not None:
152
153                if self.conditional_not_ahead(
154                    next_expr, true_expression, false_expression, f_expressions
155                ):
156                    # always on last arguments added
157                    self.copy_expression(
158                        next_expr,
159                        true_expression.expressions[-1],
160                        false_expression.expressions[-1],
161                    )
162            else:
163                true_expression.expressions.append(None)
164                false_expression.expressions.append(None)
def convert_index_access( self, next_expr: slither.core.expressions.index_access.IndexAccess, true_expression: slither.core.expressions.expression.Expression, false_expression: slither.core.expressions.expression.Expression) -> None:
166    def convert_index_access(
167        self, next_expr: IndexAccess, true_expression: Expression, false_expression: Expression
168    ) -> None:
169        # create an index access for each branch
170        #  x[if cond ? 1 : 2] -> if cond { x[1] } else { x[2] }
171        for expr in next_expr.expressions:
172            if self.conditional_not_ahead(expr, true_expression, false_expression, f_expressions):
173                self.copy_expression(
174                    expr,
175                    true_expression.expressions[-1],
176                    false_expression.expressions[-1],
177                )
179    def convert_call_expression(
180        self,
181        expression: CallExpression,
182        next_expr: Expression,
183        true_expression: Expression,
184        false_expression: Expression,
185    ) -> None:
186        # case of lib
187        # (.. ? .. : ..).add
188        if self.conditional_not_ahead(next_expr, true_expression, false_expression, f_called):
189            self.copy_expression(next_expr, true_expression.called, false_expression.called)
190
191        # In order to handle ternaries in both call options, gas and value, we return early if the
192        # conditional is not ahead to rewrite both ternaries (see `_rewrite_ternary_as_if_else`).
193        if expression.call_gas:
194            # case of (..).func{gas: .. ? .. : ..}()
195            next_expr = expression.call_gas
196            if self.conditional_not_ahead(next_expr, true_expression, false_expression, f_call_gas):
197                self.copy_expression(
198                    next_expr,
199                    true_expression.call_gas,
200                    false_expression.call_gas,
201                )
202            else:
203                return
204
205        if expression.call_value:
206            # case of (..).func{value: .. ? .. : ..}()
207            next_expr = expression.call_value
208            if self.conditional_not_ahead(
209                next_expr, true_expression, false_expression, f_call_value
210            ):
211                self.copy_expression(
212                    next_expr,
213                    true_expression.call_value,
214                    false_expression.call_value,
215                )
216            else:
217                return
218
219        true_expression._arguments = []
220        false_expression._arguments = []
221
222        for expr in expression.arguments:
223            if self.conditional_not_ahead(expr, true_expression, false_expression, f_call):
224                # always on last arguments added
225                self.copy_expression(
226                    expr,
227                    true_expression.arguments[-1],
228                    false_expression.arguments[-1],
229                )