• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CityOfZion / neo3-boa / e084b44c-1a5f-4649-92cd-d3ed06099181

05 Mar 2024 05:58PM UTC coverage: 92.023% (-0.08%) from 92.107%
e084b44c-1a5f-4649-92cd-d3ed06099181

push

circleci

Mirella de Medeiros
CU-86drpnc9z - Drop support to Python 3.10

20547 of 22328 relevant lines covered (92.02%)

1.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.58
/boa3/internal/compiler/codegenerator/codegenerator.py
1
__all__ = [
2✔
2
    'CodeGenerator'
3
]
4

5

6
from collections.abc import Sequence
2✔
7
from typing import Any
2✔
8

9
from boa3.internal import constants
2✔
10
from boa3.internal.analyser.analyser import Analyser
2✔
11
from boa3.internal.analyser.model.symbolscope import SymbolScope
2✔
12
from boa3.internal.compiler import codegenerator
2✔
13
from boa3.internal.compiler.codegenerator import optimizerhelper
2✔
14
from boa3.internal.compiler.codegenerator.codeoptimizer import CodeOptimizer
2✔
15
from boa3.internal.compiler.codegenerator.engine.stackmemento import NeoStack, StackMemento
2✔
16
from boa3.internal.compiler.codegenerator.optimizerhelper import OptimizationLevel
2✔
17
from boa3.internal.compiler.codegenerator.vmcodemapping import VMCodeMapping
2✔
18
from boa3.internal.compiler.compileroutput import CompilerOutput
2✔
19
from boa3.internal.model.builtin.builtin import Builtin
2✔
20
from boa3.internal.model.builtin.builtincallable import IBuiltinCallable
2✔
21
from boa3.internal.model.builtin.internal.innerdeploymethod import InnerDeployMethod
2✔
22
from boa3.internal.model.builtin.interop.interop import Interop
2✔
23
from boa3.internal.model.builtin.method.builtinmethod import IBuiltinMethod
2✔
24
from boa3.internal.model.event import Event
2✔
25
from boa3.internal.model.imports.importsymbol import Import
2✔
26
from boa3.internal.model.imports.package import Package
2✔
27
from boa3.internal.model.method import Method
2✔
28
from boa3.internal.model.operation.binaryop import BinaryOp
2✔
29
from boa3.internal.model.operation.operation import IOperation
2✔
30
from boa3.internal.model.operation.unaryop import UnaryOp
2✔
31
from boa3.internal.model.property import Property
2✔
32
from boa3.internal.model.symbol import ISymbol
2✔
33
from boa3.internal.model.type.classes.classtype import ClassType
2✔
34
from boa3.internal.model.type.classes.contractinterfaceclass import ContractInterfaceClass
2✔
35
from boa3.internal.model.type.classes.userclass import UserClass
2✔
36
from boa3.internal.model.type.collection.icollection import ICollectionType
2✔
37
from boa3.internal.model.type.collection.sequence.buffertype import Buffer as BufferType
2✔
38
from boa3.internal.model.type.collection.sequence.mutable.listtype import ListType
2✔
39
from boa3.internal.model.type.collection.sequence.sequencetype import SequenceType
2✔
40
from boa3.internal.model.type.primitive.bytestype import BytesType
2✔
41
from boa3.internal.model.type.primitive.primitivetype import PrimitiveType
2✔
42
from boa3.internal.model.type.primitive.strtype import StrType
2✔
43
from boa3.internal.model.type.type import IType, Type
2✔
44
from boa3.internal.model.variable import Variable
2✔
45
from boa3.internal.neo.vm.TryCode import TryCode
2✔
46
from boa3.internal.neo.vm.VMCode import VMCode
2✔
47
from boa3.internal.neo.vm.opcode import OpcodeHelper
2✔
48
from boa3.internal.neo.vm.opcode.Opcode import Opcode
2✔
49
from boa3.internal.neo.vm.opcode.OpcodeInfo import OpcodeInfo, OpcodeInformation
2✔
50
from boa3.internal.neo.vm.type.Integer import Integer
2✔
51
from boa3.internal.neo.vm.type.StackItem import StackItemType
2✔
52

53

54
class CodeGenerator:
2✔
55
    """
56
    This class is responsible for generating the Neo VM bytecode
57

58
    :ivar symbol_table: a dictionary that maps the global symbols.
59
    """
60

61
    @staticmethod
2✔
62
    def generate_code(analyser: Analyser, optimization_level: OptimizationLevel) -> CompilerOutput:
2✔
63
        """
64
        Generates the Neo VM bytecode using of the analysed Python code
65

66
        :param analyser: semantic analyser of the Python code
67
        :param optimization_level: The level of optimization that should be used after generating the code
68
        :return: the Neo VM bytecode
69
        """
70
        VMCodeMapping.reset()
2✔
71
        analyser.update_symbol_table_with_imports()
2✔
72

73
        all_imports = CodeGenerator._find_all_imports(analyser)
2✔
74
        generator = CodeGenerator(analyser.symbol_table, optimization_level)
2✔
75

76
        from boa3.internal.exception.CompilerError import CompilerError
2✔
77

78
        try:
2✔
79
            import ast
2✔
80
            from boa3.internal.compiler.codegenerator.codegeneratorvisitor import VisitorCodeGenerator
2✔
81

82
            deploy_method = (analyser.symbol_table[constants.DEPLOY_METHOD_ID]
2✔
83
                             if constants.DEPLOY_METHOD_ID in analyser.symbol_table
84
                             else None)
85
            deploy_origin_module = analyser.ast_tree
2✔
86

87
            if hasattr(deploy_method, 'origin') and deploy_method.origin in analyser.ast_tree.body:
2✔
88
                analyser.ast_tree.body.remove(deploy_method.origin)
2✔
89

90
            visitor = VisitorCodeGenerator(generator, analyser.filename, analyser.root)
2✔
91
            visitor._root_module = analyser.ast_tree
2✔
92
            visitor.visit_and_update_analyser(analyser.ast_tree, analyser)
2✔
93

94
            analyser.update_symbol_table(generator.symbol_table)
2✔
95
            generator.symbol_table.clear()
2✔
96
            generator.symbol_table.update(analyser.symbol_table.copy())
2✔
97

98
            for symbol in all_imports.values():
2✔
99
                generator.symbol_table.update(symbol.all_symbols)
2✔
100

101
                if hasattr(deploy_method, 'origin') and deploy_method.origin in symbol.ast.body:
2✔
102
                    symbol.ast.body.remove(deploy_method.origin)
×
103
                    deploy_origin_module = symbol.ast
×
104

105
                visitor.set_filename(symbol.origin)
2✔
106
                visitor.visit_and_update_analyser(symbol.ast, analyser)
2✔
107

108
                analyser.update_symbol_table(symbol.all_symbols)
2✔
109
                generator.symbol_table.clear()
2✔
110
                generator.symbol_table.update(analyser.symbol_table.copy())
2✔
111

112
            if len(generator._globals) > 0:
2✔
113
                from boa3.internal.compiler.codegenerator.initstatementsvisitor import InitStatementsVisitor
2✔
114
                deploy_stmts, static_stmts = InitStatementsVisitor.separate_global_statements(analyser.symbol_table,
2✔
115
                                                                                              visitor.global_stmts)
116

117
                deploy_method = deploy_method if deploy_method is not None else InnerDeployMethod.instance().copy()
2✔
118

119
                if len(deploy_stmts) > 0:
2✔
120
                    if_update_body = ast.parse(f"if not {list(deploy_method.args)[1]}: pass").body[0]
2✔
121
                    if_update_body.body = deploy_stmts
2✔
122
                    if_update_body.test.op = UnaryOp.Not
2✔
123
                    deploy_method.origin.body.insert(0, if_update_body)
2✔
124

125
                visitor.global_stmts = static_stmts
2✔
126

127
            if hasattr(deploy_method, 'origin'):
2✔
128
                deploy_ast = ast.parse("")
2✔
129
                deploy_ast.body = [deploy_method.origin]
2✔
130

131
                generator.symbol_table[constants.DEPLOY_METHOD_ID] = deploy_method
2✔
132
                analyser.symbol_table[constants.DEPLOY_METHOD_ID] = deploy_method
2✔
133
                visitor._tree = deploy_origin_module
2✔
134
                visitor.visit_and_update_analyser(deploy_ast, analyser)
2✔
135

136
                generator.symbol_table.clear()
2✔
137
                generator.symbol_table.update(analyser.symbol_table.copy())
2✔
138

139
            visitor.set_filename(analyser.filename)
2✔
140
            generator.can_init_static_fields = True
2✔
141
            if len(visitor.global_stmts) > 0:
2✔
142
                global_ast = ast.parse("")
2✔
143
                global_ast.body = visitor.global_stmts
2✔
144
                visitor.visit_and_update_analyser(global_ast, analyser)
2✔
145
                generator.initialized_static_fields = True
2✔
146

147
        except CompilerError:
×
148
            pass
×
149

150
        analyser.update_symbol_table(generator.symbol_table)
2✔
151
        if optimization_level > OptimizationLevel.NONE:
2✔
152
            compilation_result = generator.get_optimized_output(optimization_level)
2✔
153
        else:
154
            compilation_result = generator.output
2✔
155
        return compilation_result
2✔
156

157
    @staticmethod
2✔
158
    def _find_all_imports(analyser: Analyser) -> dict[str, Import]:
2✔
159
        imports = {}
2✔
160
        for key, symbol in analyser.symbol_table.copy().items():
2✔
161
            if isinstance(symbol, Import):
2✔
162
                importing = symbol
2✔
163
            elif isinstance(symbol, Package) and isinstance(symbol.origin, Import):
2✔
164
                importing = symbol.origin
2✔
165
                analyser.symbol_table[symbol.origin.origin] = symbol.origin
2✔
166
            else:
167
                importing = None
2✔
168

169
            if importing is not None and importing.origin not in imports:
2✔
170
                imports[importing.origin] = importing
2✔
171

172
        return imports
2✔
173

174
    def __init__(self,
2✔
175
                 symbol_table: dict[str, ISymbol],
176
                 optimization_level: OptimizationLevel = OptimizationLevel.DEFAULT
177
                 ):
178

179
        self.symbol_table: dict[str, ISymbol] = symbol_table.copy()
2✔
180
        self._optimization_level = (optimization_level
2✔
181
                                    if isinstance(optimization_level, OptimizationLevel)
182
                                    else OptimizationLevel.DEFAULT)
183
        self.additional_symbols: dict[str, ISymbol] | None = None
2✔
184

185
        self._current_method: Method = None
2✔
186
        self._current_class: Method = None
2✔
187

188
        self._missing_target: dict[int, list[VMCode]] = {}  # maps targets with address not included yet
2✔
189
        self._can_append_target: bool = True
2✔
190

191
        self._scope_stack: list[SymbolScope] = []
2✔
192
        self._global_scope = SymbolScope()
2✔
193

194
        self._current_loop: list[int] = []  # a stack with the converting loops' start addresses
2✔
195
        self._current_for: list[int] = []
2✔
196
        self._jumps_to_loop_condition: dict[int, list[int]] = {}
2✔
197

198
        self._jumps_to_loop_break: dict[int, list[int]] = {}
2✔
199
        # the indexes of boolean insertion values indicating if the jmp is from a break
200
        self._inserted_loop_breaks: dict[int, list[int]] = {}
2✔
201

202
        self._opcodes_to_remove: list[int] = []
2✔
203
        self._stack_states: StackMemento = StackMemento()  # simulates neo execution stack
2✔
204

205
        self.can_init_static_fields: bool = False
2✔
206
        self.initialized_static_fields: bool = False
2✔
207

208
        self._static_vars: list | None = None
2✔
209
        self._global_vars: list | None = None
2✔
210

211
    @property
2✔
212
    def bytecode(self) -> bytes:
2✔
213
        """
214
        Gets the bytecode of the translated code
215

216
        :return: the generated bytecode
217
        """
218
        output = self.output
2✔
219
        return output.bytecode
2✔
220

221
    def _remove_opcodes_without_target(self):
2✔
222
        opcodes = VMCodeMapping.instance().get_opcodes(self._opcodes_to_remove)
2✔
223
        self.set_code_targets()
2✔
224
        VMCodeMapping.instance().remove_opcodes_by_code(opcodes)
2✔
225
        self._opcodes_to_remove.clear()
2✔
226

227
    @property
2✔
228
    def output(self) -> CompilerOutput:
2✔
229
        """
230
        Gets the bytecode of the translated code
231

232
        :return: the generated bytecode
233
        """
234
        self._remove_opcodes_without_target()
2✔
235
        return VMCodeMapping.instance().result()
2✔
236

237
    def get_optimized_output(self, optimization_level: OptimizationLevel) -> CompilerOutput:
2✔
238
        self._remove_opcodes_without_target()
2✔
239

240
        optimizer = CodeOptimizer(symbol_table=self.symbol_table)
2✔
241
        optimizer.optimize(optimization_level)
2✔
242

243
        return VMCodeMapping.instance().result()
2✔
244

245
    @property
2✔
246
    def last_code(self) -> VMCode | None:
2✔
247
        """
248
        Gets the last code in the bytecode
249

250
        :return: the last code. If the bytecode is empty, returns None
251
        :rtype: VMCode or None
252
        """
253
        if len(VMCodeMapping.instance().codes) > 0:
2✔
254
            return VMCodeMapping.instance().codes[-1]
2✔
255
        else:
256
            return None
2✔
257

258
    @property
2✔
259
    def _stack(self) -> NeoStack:
2✔
260
        return self._stack_states.current_stack
2✔
261

262
    @property
2✔
263
    def stack_size(self) -> int:
2✔
264
        """
265
        Gets the size of the stack
266

267
        :return: the size of the stack of converted values
268
        """
269
        return len(self._stack)
2✔
270

271
    def _stack_append(self, value_type: IType):
2✔
272
        self._stack_states.append(value_type, self.last_code)
2✔
273

274
    def _stack_pop(self, index: int = -1) -> IType:
2✔
275
        return self._stack_states.pop(self.last_code, index)
2✔
276

277
    @property
2✔
278
    def last_code_start_address(self) -> int:
2✔
279
        """
280
        Gets the first address from last code in the bytecode
281

282
        :return: the last code's first address
283
        """
284
        instance = VMCodeMapping.instance()
2✔
285
        if len(instance.codes) > 0:
2✔
286
            return instance.get_start_address(instance.codes[-1])
2✔
287
        else:
288
            return 0
2✔
289

290
    @property
2✔
291
    def bytecode_size(self) -> int:
2✔
292
        """
293
        Gets the current bytecode size
294

295
        :return: the current bytecode size
296
        """
297
        return VMCodeMapping.instance().bytecode_size
2✔
298

299
    @property
2✔
300
    def _args(self) -> list[str]:
2✔
301
        """
302
        Gets a list with the arguments names of the current method
303

304
        :return: A list with the arguments names
305
        """
306
        return [] if self._current_method is None else list(self._current_method.args.keys())
2✔
307

308
    @property
2✔
309
    def _locals(self) -> list[str]:
2✔
310
        """
311
        Gets a list with the variables names in the scope of the current method
312

313
        :return: A list with the variables names
314
        """
315
        return [] if self._current_method is None else list(self._current_method.locals.keys())
2✔
316

317
    @property
2✔
318
    def _globals(self) -> list[str]:
2✔
319
        return self._module_variables(True)
2✔
320

321
    @property
2✔
322
    def _statics(self) -> list[str]:
2✔
323
        return self._module_variables(False)
2✔
324

325
    def _module_variables(self, modified_variable: bool) -> list[str]:
2✔
326
        """
327
        Gets a list with the variables name in the global scope
328

329
        :return: A list with the variables names
330
        """
331
        if modified_variable:
2✔
332
            vars_map = self._global_vars
2✔
333
        else:
334
            vars_map = self._static_vars
2✔
335

336
        module_global_variables = []
2✔
337
        module_global_ids = []
2✔
338
        result_global_vars = []
2✔
339
        for var_id, var in self.symbol_table.items():
2✔
340
            if (
2✔
341
                isinstance(var, Variable) and
342
                var.is_reassigned == modified_variable and
343
                (modified_variable or self.store_constant_variable(var)) and
344
                var not in result_global_vars
345
            ):
346
                module_global_variables.append((var_id, var))
2✔
347
                module_global_ids.append(var_id)
2✔
348
                result_global_vars.append(var)
2✔
349

350
        class_with_class_variables = []
2✔
351
        class_with_variables_ids = []
2✔
352
        for class_id, class_symbol in self.symbol_table.items():
2✔
353
            if isinstance(class_symbol, UserClass) and len(class_symbol.class_variables) > 0:
2✔
354
                class_with_class_variables.append((class_id, class_symbol))
2✔
355
                class_with_variables_ids.append(class_id)
2✔
356

357
        if not self.can_init_static_fields:
2✔
358
            for imported in self.symbol_table.values():
2✔
359
                if isinstance(imported, Import):
2✔
360
                    # tried to use set and just update, but we need the variables to be ordered
361
                    for var_id, var in imported.variables.items():
2✔
362
                        if (isinstance(var, Variable)
2✔
363
                                and var.is_reassigned == modified_variable
364
                                and var_id not in module_global_ids
365
                                and (modified_variable or self.store_constant_variable(var))
366
                                and var not in result_global_vars):
367
                            module_global_variables.append((var_id, var))
2✔
368
                            module_global_ids.append(var_id)
2✔
369
                            result_global_vars.append(var)
2✔
370

371
        if modified_variable:
2✔
372
            result_map = module_global_variables
2✔
373
            result = module_global_ids
2✔
374
        else:
375
            result_map = module_global_variables + class_with_class_variables
2✔
376
            result_global_vars = result_global_vars + [classes for (class_id, classes) in class_with_class_variables]
2✔
377
            result = module_global_ids + class_with_variables_ids
2✔
378

379
        if vars_map != result_map:
2✔
380
            if vars_map is None:
2✔
381
                # save to keep the same order in future accesses
382
                if modified_variable:
2✔
383
                    self._global_vars = result_map
2✔
384
                else:
385
                    self._static_vars = result_map
2✔
386

387
            else:
388
                # reorder to keep the same order as the first access
389
                if len(result_map) > len(vars_map):
2✔
390
                    additional_items = []
2✔
391
                    vars_list = [var for var_id, var in vars_map]
2✔
392
                    for index, var in enumerate(result_global_vars):
2✔
393
                        if var in vars_list:
2✔
394
                            var_index = vars_list.index(var)
2✔
395
                            vars_map[var_index] = result_map[index]
2✔
396
                        else:
397
                            additional_items.append(result_map[index])
2✔
398
                    vars_map.extend(additional_items)
2✔
399

400
                    pre_reordered_ids = [var_id for (var_id, var) in vars_map]
2✔
401
                else:
402
                    original_ids = []
2✔
403
                    for value in result:
2✔
404
                        split = value.split(constants.VARIABLE_NAME_SEPARATOR)
2✔
405
                        if len(split) > 1:
2✔
406
                            new_index = split[-1]
2✔
407
                        else:
408
                            new_index = value
×
409
                        original_ids.append(new_index)
2✔
410

411
                    pre_reordered_ids = [var_id for (var_id, var) in vars_map]
2✔
412
                    for index, (value, var) in enumerate(vars_map):
2✔
413
                        if value not in result:
2✔
414
                            if var in result_global_vars:
2✔
415
                                var_index = result_global_vars.index(var)
2✔
416
                                new_value = result_map[var_index]
2✔
417
                            else:
418
                                var_index = original_ids.index(value)
×
419
                                new_value = result_map[var_index]
×
420

421
                            vars_map[index] = new_value
2✔
422

423
                # add new symbols at the end always
424
                reordered_ids = [var_id for (var_id, var) in vars_map]
2✔
425
                additional_items = []
2✔
426
                for index, var_id in enumerate(result):
2✔
427
                    if var_id not in reordered_ids and var_id not in pre_reordered_ids:
2✔
428
                        additional_items.append(var_id)
×
429
                        vars_map.append(result_map[index])
×
430

431
                result = reordered_ids + additional_items
2✔
432

433
        return result
2✔
434

435
    @property
2✔
436
    def _current_scope(self) -> SymbolScope:
2✔
437
        return self._scope_stack[-1] if len(self._scope_stack) > 0 else self._global_scope
2✔
438

439
    # region Optimization properties
440

441
    def store_constant_variable(self, var: Variable) -> bool:
2✔
442
        return optimizerhelper.is_storing_static_variable(self._optimization_level, var)
2✔
443

444
    # endregion
445

446
    def is_none_inserted(self) -> bool:
2✔
447
        """
448
        Checks whether the last insertion is null
449

450
        :return: whether the last value is null
451
        """
452
        return (self.last_code.opcode is Opcode.PUSHNULL or
2✔
453
                (len(self._stack) > 0 and self._stack[-1] is Type.none))
454

455
    def get_symbol(self, identifier: str, scope: ISymbol | None = None, is_internal: bool = False) -> tuple[str, ISymbol]:
2✔
456
        """
457
        Gets a symbol in the symbol table by its id
458

459
        :param identifier: id of the symbol
460
        :return: the symbol if exists. Symbol None otherwise
461
        """
462
        cur_symbol_table = self.symbol_table.copy()
2✔
463
        if isinstance(self.additional_symbols, dict):
2✔
464
            cur_symbol_table.update(self.additional_symbols)
×
465

466
        found_id = None
2✔
467
        found_symbol = None
2✔
468
        if len(self._scope_stack) > 0:
2✔
469
            for symbol_scope in self._scope_stack:
2✔
470
                if identifier in symbol_scope:
2✔
471
                    found_id, found_symbol = identifier, symbol_scope[identifier]
2✔
472
                    break
2✔
473

474
        if found_id is None:
2✔
475
            if scope is not None and hasattr(scope, 'symbols') and isinstance(scope.symbols, dict):
2✔
476
                if identifier in scope.symbols and isinstance(scope.symbols[identifier], ISymbol):
×
477
                    found_id, found_symbol = identifier, scope.symbols[identifier]
×
478
            else:
479
                if self._current_method is not None and identifier in self._current_method.symbols:
2✔
480
                    found_id, found_symbol = identifier, self._current_method.symbols[identifier]
2✔
481
                elif identifier in cur_symbol_table:
2✔
482
                    found_id, found_symbol = identifier, cur_symbol_table[identifier]
2✔
483
                else:
484
                    # the symbol may be a built-in. If not, returns None
485
                    symbol = Builtin.get_symbol(identifier)
2✔
486
                    if symbol is None:
2✔
487
                        symbol = Interop.get_symbol(identifier)
2✔
488

489
                    if symbol is not None:
2✔
490
                        found_id, found_symbol = identifier, symbol
2✔
491

492
                    elif not isinstance(identifier, str):
2✔
493
                        found_id, found_symbol = identifier, symbol
×
494

495
                    else:
496
                        split = identifier.split(constants.ATTRIBUTE_NAME_SEPARATOR)
2✔
497
                        if len(split) > 1:
2✔
498
                            attribute, symbol_id = constants.ATTRIBUTE_NAME_SEPARATOR.join(split[:-1]), split[-1]
2✔
499
                            another_attr_id, attr = self.get_symbol(attribute, is_internal=is_internal)
2✔
500
                            if hasattr(attr, 'symbols') and symbol_id in attr.symbols:
2✔
501
                                found_id, found_symbol = symbol_id, attr.symbols[symbol_id]
2✔
502
                            elif isinstance(attr, Package) and symbol_id in attr.inner_packages:
2✔
503
                                found_id, found_symbol = symbol_id, attr.inner_packages[symbol_id]
2✔
504

505
                        if found_id is None and is_internal:
2✔
506
                            from boa3.internal.model import imports
×
507
                            found_symbol = imports.builtin.get_internal_symbol(identifier)
×
508
                            if isinstance(found_symbol, ISymbol):
×
509
                                found_id = identifier
×
510

511
        if found_id is not None:
2✔
512
            if isinstance(found_symbol, Variable) and not found_symbol.is_reassigned and found_id not in self._statics:
2✔
513
                # verifies if it's a static variable with a unique name
514
                for static_id, static_var in self._static_vars:
2✔
515
                    if found_symbol == static_var:
2✔
516
                        found_id = static_id
×
517
                        break
×
518

519
            return found_id, found_symbol
2✔
520
        return identifier, Type.none
2✔
521

522
    def initialize_static_fields(self) -> bool:
2✔
523
        """
524
        Converts the signature of the method
525

526
        :return: whether there are static fields to be initialized
527
        """
528
        if not self.can_init_static_fields:
2✔
529
            return False
2✔
530
        if self.initialized_static_fields:
2✔
531
            return False
×
532

533
        num_static_fields = len(self._statics)
2✔
534
        if num_static_fields > 0:
2✔
535
            init_data = bytearray([num_static_fields])
2✔
536
            self.__insert1(OpcodeInfo.INITSSLOT, init_data)
2✔
537

538
            if constants.INITIALIZE_METHOD_ID in self.symbol_table:
2✔
539
                from boa3.internal.helpers import get_auxiliary_name
×
540
                method = self.symbol_table.pop(constants.INITIALIZE_METHOD_ID)
×
541
                new_id = get_auxiliary_name(constants.INITIALIZE_METHOD_ID, method)
×
542
                self.symbol_table[new_id] = method
×
543

544
            init_method = Method(is_public=True)
2✔
545
            init_method.init_bytecode = self.last_code
2✔
546
            self.symbol_table[constants.INITIALIZE_METHOD_ID] = init_method
2✔
547

548
        return num_static_fields > 0
2✔
549

550
    def end_initialize(self):
2✔
551
        """
552
        Converts the signature of the method
553
        """
554
        self.insert_return()
2✔
555
        self.initialized_static_fields = True
2✔
556

557
        if constants.INITIALIZE_METHOD_ID in self.symbol_table:
2✔
558
            init_method = self.symbol_table[constants.INITIALIZE_METHOD_ID]
2✔
559
            init_method.end_bytecode = self.last_code
2✔
560

561
    def convert_begin_method(self, method: Method):
2✔
562
        """
563
        Converts the signature of the method
564

565
        :param method: method that is being converted
566
        """
567
        new_variable_scope = self._scope_stack[-1].copy() if len(self._scope_stack) > 0 else SymbolScope()
2✔
568
        self._scope_stack.append(new_variable_scope)
2✔
569

570
        num_args: int = len(method.args)
2✔
571
        num_vars: int = len(method.locals)
2✔
572

573
        method.init_address = VMCodeMapping.instance().bytecode_size
2✔
574
        if num_args > 0 or num_vars > 0:
2✔
575
            init_data = bytearray([num_vars, num_args])
2✔
576
            self.__insert1(OpcodeInfo.INITSLOT, init_data)
2✔
577
            method.init_bytecode = self.last_code
2✔
578
        self._current_method = method
2✔
579

580
    def convert_end_method(self, method_id: str | None = None):
2✔
581
        """
582
        Converts the end of the method
583
        """
584
        if (self._current_method.init_bytecode is None
2✔
585
                and self._current_method.init_address in VMCodeMapping.instance().code_map):
586
            self._current_method.init_bytecode = VMCodeMapping.instance().code_map[self._current_method.init_address]
2✔
587

588
        if self.last_code.opcode is not Opcode.RET or self._check_codes_with_target():
2✔
589
            if self._current_method.is_init:
2✔
590
                # return the built object if it's a constructor
591
                self_id, self_value = list(self._current_method.args.items())[0]
2✔
592
                self.convert_load_variable(self_id, self_value)
2✔
593

594
            self.insert_return()
2✔
595

596
        self._current_method.end_bytecode = self.last_code
2✔
597
        self._current_method = None
2✔
598
        self._stack.clear()
2✔
599

600
        function_variable_scope = self._scope_stack.pop()
2✔
601

602
    def insert_return(self):
2✔
603
        """
604
        Insert the return statement
605
        """
606
        self.__insert1(OpcodeInfo.RET)
2✔
607

608
    def insert_not(self):
2✔
609
        """
610
        Insert a `not` to change the value of a bool
611
        """
612
        self.__insert1(OpcodeInfo.NOT)
×
613

614
    def insert_nop(self):
2✔
615
        """
616
        Insert a NOP opcode
617
        """
618
        self.__insert1(OpcodeInfo.NOP)
2✔
619

620
    def insert_sys_call(self, sys_call_id: bytes):
2✔
621
        """
622
        Insert a SYSCALL opcode call
623
        """
624
        self.__insert1(OpcodeInfo.SYSCALL, sys_call_id)
2✔
625

626
    def convert_begin_while(self, is_for: bool = False) -> int:
2✔
627
        """
628
        Converts the beginning of the while statement
629

630
        :param is_for: whether the loop is a for loop or not
631
        :return: the address of the while first opcode
632
        """
633
        # it will be updated when the while ends
634
        self._insert_jump(OpcodeInfo.JMP)
2✔
635

636
        start_address = self.last_code_start_address
2✔
637
        self._current_loop.append(start_address)
2✔
638
        if is_for:
2✔
639
            self._current_for.append(start_address)
2✔
640

641
        return start_address
2✔
642

643
    def convert_end_while(self, start_address: int, test_address: int, *, is_internal: bool = False) -> int:
2✔
644
        """
645
        Converts the end of the while statement
646

647
        :param start_address: the address of the while first opcode
648
        :param test_address: the address of the while test fist opcode
649
        :param is_internal: whether it was called when generating other implemented symbols
650
        """
651
        return self.convert_end_loop(start_address, test_address, False, is_internal=is_internal)
2✔
652

653
    def convert_begin_for(self) -> int:
2✔
654
        """
655
        Converts the beginning of the for statement
656

657
        :return: the address of the for first opcode
658
        """
659
        is_neo_iterator = len(self._stack) > 0 and Interop.Iterator.is_type_of(self._stack[-1])
2✔
660
        if is_neo_iterator:
2✔
661
            self.duplicate_stack_top_item()
2✔
662
        else:
663
            self.convert_literal(0)
2✔
664

665
        address = self.convert_begin_while(True)
2✔
666

667
        if is_neo_iterator:
2✔
668
            self.duplicate_stack_top_item()
2✔
669
            self.convert_builtin_method_call(Interop.IteratorValue)
2✔
670
        else:
671
            self.duplicate_stack_item(2)  # duplicate for sequence
2✔
672
            self.duplicate_stack_item(2)  # duplicate for index
2✔
673
            self.convert_get_item()
2✔
674
        return address
2✔
675

676
    def convert_end_for(self, start_address: int, is_internal: bool = False) -> int:
2✔
677
        """
678
        Converts the end of the for statement
679

680
        :param start_address: the address of the for first opcode
681
        :param is_internal: whether it was called when generating other implemented symbols
682
        :return: the address of the loop condition
683
        """
684
        is_neo_iterator = len(self._stack) > 0 and Interop.Iterator.is_type_of(self._stack[-1])
2✔
685
        if not is_neo_iterator:
2✔
686
            self.__insert1(OpcodeInfo.INC)      # index += 1
2✔
687
            if len(self._stack) < 1 or self._stack[-1] is not Type.int:
2✔
688
                self._stack_append(Type.int)
2✔
689

690
        for_increment = self.last_code_start_address
2✔
691
        test_address = VMCodeMapping.instance().bytecode_size
2✔
692
        self._update_continue_jumps(start_address, for_increment)
2✔
693

694
        if is_neo_iterator:
2✔
695
            self.duplicate_stack_top_item()
2✔
696
            self.convert_builtin_method_call(Interop.IteratorNext)
2✔
697
        else:
698
            self.duplicate_stack_top_item()     # dup index and sequence
2✔
699
            self.duplicate_stack_item(3)
2✔
700
            self.convert_builtin_method_call(Builtin.Len)
2✔
701
            self.convert_operation(BinaryOp.Lt)  # continue loop condition: index < len(sequence)
2✔
702

703
        self.convert_end_loop(start_address, test_address, True, is_internal=is_internal)
2✔
704

705
        return test_address
2✔
706

707
    def convert_end_loop(self, start_address: int, test_address: int, is_for: bool, is_internal: bool = False) -> int:
2✔
708
        """
709
        Converts the end of a loop statement
710

711
        :param start_address: the address of the while first opcode
712
        :param test_address: the address of the while test fist opcode
713
        :param is_for: whether the loop is a for loop or not
714
        :param is_internal: whether it was called when generating other implemented symbols
715
        """
716
        # updates the begin jmp with the target address
717
        self._update_jump(start_address, test_address)
2✔
718
        self._update_continue_jumps(start_address, test_address)
2✔
719

720
        # inserts end jmp
721
        while_begin: VMCode = VMCodeMapping.instance().code_map[start_address]
2✔
722
        while_body: int = VMCodeMapping.instance().get_end_address(while_begin) + 1
2✔
723
        end_jmp_to: int = while_body - VMCodeMapping.instance().bytecode_size
2✔
724
        self._insert_jump(OpcodeInfo.JMPIF, end_jmp_to)
2✔
725

726
        self._current_loop.pop()
2✔
727

728
        is_break_pos = self.bytecode_size
2✔
729
        self.convert_literal(False)  # is not break
2✔
730
        is_break_end = self.last_code_start_address
2✔
731
        self._update_break_jumps(start_address)
2✔
732

733
        if is_for:
2✔
734
            self._current_for.pop()
2✔
735
            reverse_to_drop_pos = self.last_code_start_address
2✔
736
            self.swap_reverse_stack_items(3)
2✔
737
            reverse_to_drop_end = self.last_code_start_address
2✔
738

739
            self.remove_stack_top_item()    # removes index and sequence from stack
2✔
740
            self.remove_stack_top_item()
2✔
741

742
            self._insert_loop_break_addresses(start_address, reverse_to_drop_pos, reverse_to_drop_end, self.bytecode_size)
2✔
743

744
        last_opcode = self.bytecode_size
2✔
745
        self._insert_loop_break_addresses(start_address, is_break_pos, is_break_end, last_opcode)
2✔
746
        self._insert_jump(OpcodeInfo.JMPIF)
2✔
747

748
        if is_internal:
2✔
749
            self.convert_end_loop_else(start_address, self.last_code_start_address)
2✔
750
        return last_opcode
2✔
751

752
    def convert_end_loop_else(self, start_address: int, else_begin: int, has_else: bool = False, is_for: bool = False):
2✔
753
        """
754
        Updates the break loops jumps
755

756
        :param start_address: the address of the loop first opcode
757
        :param else_begin: the address of the else first opcode. Equals to code size if has_else is False
758
        :param has_else: whether this loop has an else branch
759
        :param is_for: whether the loop is a for loop or not
760
        """
761
        if start_address in self._jumps_to_loop_break:
2✔
762
            is_loop_insertions = []
2✔
763
            if start_address in self._inserted_loop_breaks:
2✔
764
                is_loop_insertions = self._inserted_loop_breaks.pop(start_address)
2✔
765
            is_loop_insertions.append(else_begin)
2✔
766

767
            if not has_else:
2✔
768
                self._opcodes_to_remove.extend(is_loop_insertions)
2✔
769
            else:
770
                min_break_addresses = 4 if is_for else 3
2✔
771
                if (start_address in self._jumps_to_loop_break
2✔
772
                        and len(self._jumps_to_loop_break[start_address]) < 2
773
                        and len(is_loop_insertions) < min_break_addresses):
774
                    # if len is less than 2, it means it has no breaks or the only break is else branch begin
775
                    # so it can remove the jump in the beginning of else branch
776
                    self._opcodes_to_remove.extend(is_loop_insertions)
2✔
777
                self._update_jump(else_begin, VMCodeMapping.instance().bytecode_size)
2✔
778

779
    def convert_begin_if(self) -> int:
2✔
780
        """
781
        Converts the beginning of the if statement
782

783
        :return: the address of the if first opcode
784
        """
785
        # it will be updated when the if ends
786
        self._insert_jump(OpcodeInfo.JMPIFNOT)
2✔
787
        return VMCodeMapping.instance().get_start_address(self.last_code)
2✔
788

789
    def convert_begin_else(self, start_address: int, insert_jump: bool = False, is_internal: bool = False) -> int:
2✔
790
        """
791
        Converts the beginning of the if else statement
792

793
        :param start_address: the address of the if first opcode
794
        :param insert_jump: whether it should be included a jump to the end before the else branch
795
        :return: the address of the if else first opcode
796
        """
797
        # it will be updated when the if ends
798
        self._insert_jump(OpcodeInfo.JMP, insert_jump=insert_jump)
2✔
799

800
        # updates the begin jmp with the target address
801
        self._update_jump(start_address, VMCodeMapping.instance().bytecode_size)
2✔
802
        if is_internal:
2✔
803
            self._stack_states.restore_state(start_address + 1)
2✔
804

805
        return self.last_code_start_address
2✔
806

807
    def convert_end_if(self, start_address: int, is_internal: bool = False):
2✔
808
        """
809
        Converts the end of the if statement
810

811
        :param start_address: the address of the if first opcode
812
        """
813
        # updates the begin jmp with the target address
814
        self._update_jump(start_address, VMCodeMapping.instance().bytecode_size)
2✔
815
        if is_internal:
2✔
816
            self._stack_states.restore_state(start_address)
2✔
817

818
    def convert_begin_try(self) -> int:
2✔
819
        """
820
        Converts the beginning of the try statement
821

822
        :return: the address of the try first opcode
823
        """
824
        # it will be updated when the while ends
825
        self.__insert_code(TryCode())
2✔
826

827
        return self.last_code_start_address
2✔
828

829
    def convert_try_except(self, exception_id: str | None) -> int:
2✔
830
        """
831
        Converts the end of the try statement
832

833
        :param exception_id: the name identifier of the exception
834
        :type exception_id: str or None
835

836
        :return: the last address from try body
837
        """
838
        self._insert_jump(OpcodeInfo.JMP)
2✔
839
        last_try_code = self.last_code_start_address
2✔
840

841
        self._stack_append(Type.exception)  # when reaching the except body, an exception was raised
2✔
842
        if exception_id is None:
2✔
843
            self.remove_stack_top_item()
2✔
844

845
        return last_try_code
2✔
846

847
    def convert_end_try(self, start_address: int,
2✔
848
                        end_address: int | None = None,
849
                        else_address: int | None = None) -> int:
850
        """
851
        Converts the end of the try statement
852

853
        :param start_address: the address of the try first opcode
854
        :param end_address: the address of the try last opcode. If it is None, there's no except body.
855
        :param else_address: the address of the try else. If it is None, there's no else body.
856
        :return: the last address of the except body
857
        """
858
        self.__insert1(OpcodeInfo.ENDTRY)
2✔
859
        if end_address is not None:
2✔
860
            vmcode_mapping_instance = VMCodeMapping.instance()
2✔
861

862
            try_vm_code = vmcode_mapping_instance.get_code(start_address)
2✔
863
            try_jump = vmcode_mapping_instance.get_code(end_address)
2✔
864

865
            except_start_address = vmcode_mapping_instance.get_end_address(try_jump) + 1
2✔
866
            except_start_code = vmcode_mapping_instance.get_code(except_start_address)
2✔
867

868
            if isinstance(try_vm_code, TryCode):
2✔
869
                try_vm_code.set_except_code(except_start_code)
2✔
870
            self._update_jump(else_address if else_address is not None else end_address, self.last_code_start_address)
2✔
871

872
        return self.last_code_start_address
2✔
873

874
    def convert_end_try_finally(self, last_address: int, start_address: int, has_try_body: bool = False):
2✔
875
        """
876
        Converts the end of the try finally statement
877

878
        :param last_address: the address of the try except last opcode.
879
        :param start_address: the address of the try first opcode
880
        :param has_try_body: whether this try statement has a finally body.
881
        :return: the last address of the except body
882
        """
883
        if has_try_body:
2✔
884
            self.__insert1(OpcodeInfo.ENDFINALLY)
2✔
885
            vmcode_mapping_instance = VMCodeMapping.instance()
2✔
886

887
            try_vm_code = vmcode_mapping_instance.get_code(start_address)
2✔
888
            try_last_code = vmcode_mapping_instance.get_code(last_address)
2✔
889

890
            finally_start_address = vmcode_mapping_instance.get_end_address(try_last_code) + 1
2✔
891
            finally_start_code = vmcode_mapping_instance.get_code(finally_start_address)
2✔
892

893
            if isinstance(try_vm_code, TryCode):
2✔
894
                try_vm_code.set_finally_code(finally_start_code)
2✔
895
            self._update_jump(vmcode_mapping_instance.bytecode_size, self.last_code_start_address)
2✔
896

897
        self._update_jump(last_address, VMCodeMapping.instance().bytecode_size)
2✔
898

899
    def fix_negative_index(self, value_index: int = None, test_is_negative=True):
2✔
900
        self._can_append_target = not self._can_append_target
2✔
901

902
        value_code = self.last_code_start_address
2✔
903
        size = VMCodeMapping.instance().bytecode_size
2✔
904

905
        if test_is_negative:
2✔
906
            self.duplicate_stack_top_item()
2✔
907
            self.__insert1(OpcodeInfo.SIGN)
2✔
908
            self.convert_literal(-1)
2✔
909

910
            jmp_address = VMCodeMapping.instance().bytecode_size
2✔
911
            self._insert_jump(OpcodeInfo.JMPNE)     # if index < 0
2✔
912

913
        state = self._stack_states.get_state(value_index) if isinstance(value_index, int) else self._stack
2✔
914
        # get position of collection relative to top
915
        index_of_last = -1
2✔
916
        for index, value in reversed(list(enumerate(state))):
2✔
917
            if isinstance(value, ICollectionType):
2✔
918
                index_of_last = index
2✔
919
                break
2✔
920

921
        if index_of_last >= 0:
2✔
922
            pos_from_top = len(state) - index_of_last
2✔
923
        else:
924
            pos_from_top = 2
2✔
925

926
        self.duplicate_stack_item(pos_from_top)     # index += len(array)
2✔
927
        self.convert_builtin_method_call(Builtin.Len)
2✔
928
        self.convert_operation(BinaryOp.Add)
2✔
929

930
        if test_is_negative:
2✔
931
            if not isinstance(value_index, int):
2✔
932
                value_index = VMCodeMapping.instance().bytecode_size
2✔
933
            jmp_target = value_index if value_index < size else VMCodeMapping.instance().bytecode_size
2✔
934
            self._update_jump(jmp_address, jmp_target)
2✔
935

936
            VMCodeMapping.instance().move_to_end(value_index, value_code)
2✔
937

938
        self._can_append_target = not self._can_append_target
2✔
939

940
    def fix_index_out_of_range(self, has_another_index_in_stack: bool):
2✔
941
        """
942
        Will fix a negative index to 0 or an index greater than the sequence length to the length.
943

944
        For example: [0, 1, 2][-999:999] is the same as [0, 1, 2][0:3]
945

946
        :param has_another_index_in_stack: whether the stack is [..., Sequence, index, index] or [..., Sequence, index].
947
        """
948
        # if index is still negative, then it should be 0
949
        self.duplicate_stack_item(2 if has_another_index_in_stack else 1)
2✔
950
        self.__insert1(OpcodeInfo.SIGN)
2✔
951
        self.convert_literal(-1)
2✔
952
        jmp_address = VMCodeMapping.instance().bytecode_size
2✔
953
        self._insert_jump(OpcodeInfo.JMPNE)  # if index < 0, then index = 0
2✔
954

955
        if has_another_index_in_stack:
2✔
956
            self.swap_reverse_stack_items(2)
2✔
957
        self.remove_stack_top_item()
2✔
958
        self.convert_literal(0)
2✔
959
        if has_another_index_in_stack:
2✔
960
            self.swap_reverse_stack_items(2)
2✔
961
        jmp_target = VMCodeMapping.instance().bytecode_size
2✔
962
        self._update_jump(jmp_address, jmp_target)
2✔
963

964
        # index can not be greater than len(string)
965
        self.duplicate_stack_item(3 if has_another_index_in_stack else 2)
2✔
966
        self.convert_builtin_method_call(Builtin.Len)
2✔
967
        self.__insert1(
2✔
968
            OpcodeInfo.MIN)  # the builtin MinMethod accepts more than 2 arguments, that's why this Opcode is being directly inserted
969
        self._stack_pop()
2✔
970

971
    def fix_index_negative_stride(self):
2✔
972
        """
973
        If stride is negative, then the array was reversed, thus, lower and upper should be changed
974
        accordingly.
975
        The Opcodes below will only fix 1 index.
976
        array[lower:upper:-1] == reversed_array[len(array)-lower-1:len(array)-upper-1]
977
        If lower or upper was None, then it's not necessary to change its values.
978
        """
979
        # top array should be: len(array), index
980
        self.convert_builtin_method_call(Builtin.Len)
2✔
981
        self.swap_reverse_stack_items(2)
2✔
982
        self.convert_operation(BinaryOp.Sub)
2✔
983
        self.__insert1(OpcodeInfo.DEC)
2✔
984

985
    def convert_loop_continue(self):
2✔
986
        loop_start = self._current_loop[-1]
2✔
987
        self._insert_jump(OpcodeInfo.JMP)
2✔
988
        continue_address = self.last_code_start_address
2✔
989

990
        if loop_start not in self._jumps_to_loop_condition:
2✔
991
            self._jumps_to_loop_condition[loop_start] = [continue_address]
2✔
992
        else:
993
            self._jumps_to_loop_condition[loop_start].append(continue_address)
×
994

995
    def _update_continue_jumps(self, loop_start_address, loop_test_address):
2✔
996
        if loop_start_address in self._jumps_to_loop_condition:
2✔
997
            jump_addresses = self._jumps_to_loop_condition.pop(loop_start_address)
2✔
998
            for address in jump_addresses:
2✔
999
                self._update_jump(address, loop_test_address)
2✔
1000

1001
    def convert_loop_break(self):
2✔
1002
        loop_start = self._current_loop[-1]
2✔
1003
        is_break_pos = self.bytecode_size
2✔
1004
        self.convert_literal(True)  # is break
2✔
1005
        self._stack_pop()
2✔
1006
        is_break_end = self.last_code_start_address
2✔
1007
        self._insert_jump(OpcodeInfo.JMP)
2✔
1008
        break_address = self.last_code_start_address
2✔
1009

1010
        self._insert_loop_break_addresses(loop_start, is_break_pos, is_break_end, break_address)
2✔
1011

1012
    def _insert_loop_break_addresses(self, loop_start: int, is_break_start: int, is_break_end: int, break_address: int):
2✔
1013
        if loop_start not in self._jumps_to_loop_break:
2✔
1014
            self._jumps_to_loop_break[loop_start] = [break_address]
2✔
1015
        elif break_address not in self._jumps_to_loop_break[loop_start]:
2✔
1016
            self._jumps_to_loop_break[loop_start].append(break_address)
2✔
1017

1018
        is_break_instructions = VMCodeMapping.instance().get_addresses(is_break_start, is_break_end)
2✔
1019

1020
        if loop_start not in self._inserted_loop_breaks:
2✔
1021
            self._inserted_loop_breaks[loop_start] = is_break_instructions
2✔
1022
        else:
1023
            loop_breaks_list = self._inserted_loop_breaks[loop_start]
2✔
1024
            for address in is_break_instructions:
2✔
1025
                # don't include duplicated addresses
1026
                if address not in loop_breaks_list:
2✔
1027
                    loop_breaks_list.append(address)
2✔
1028

1029
    def _update_break_jumps(self, loop_start_address) -> int:
2✔
1030
        jump_target = VMCodeMapping.instance().bytecode_size
2✔
1031

1032
        if loop_start_address in self._jumps_to_loop_break:
2✔
1033
            jump_addresses = self._jumps_to_loop_break.pop(loop_start_address)
2✔
1034
            for address in jump_addresses:
2✔
1035
                self._update_jump(address, jump_target)
2✔
1036

1037
    def convert_literal(self, value: Any) -> int:
2✔
1038
        """
1039
        Converts a literal value
1040

1041
        :param value: the value to be converted
1042
        :return: the converted value's start address in the bytecode
1043
        """
1044
        start_address = VMCodeMapping.instance().bytecode_size
2✔
1045
        if isinstance(value, bool):
2✔
1046
            self.convert_bool_literal(value)
2✔
1047
        elif isinstance(value, int):
2✔
1048
            self.convert_integer_literal(value)
2✔
1049
        elif isinstance(value, str):
2✔
1050
            self.convert_string_literal(value)
2✔
1051
        elif value is None:
2✔
1052
            self.insert_none()
2✔
1053
        elif isinstance(value, (bytes, bytearray)):
2✔
1054
            self.convert_byte_array(value)
2✔
1055
        elif isinstance(value, Sequence):
2✔
1056
            self.convert_sequence_literal(value)
2✔
1057
        elif isinstance(value, dict):
2✔
1058
            self.convert_dict_literal(value)
2✔
1059
        else:
1060
            # it's not a type that is supported by neo-boa
1061
            raise NotImplementedError
×
1062
        return start_address
2✔
1063

1064
    def convert_integer_literal(self, value: int):
2✔
1065
        """
1066
        Converts an integer literal value
1067

1068
        :param value: the value to be converted
1069
        """
1070
        opcode = OpcodeHelper.get_literal_push(value)
2✔
1071
        if opcode is not None:
2✔
1072
            op_info: OpcodeInformation = OpcodeInfo.get_info(opcode)
2✔
1073
            self.__insert1(op_info)
2✔
1074
            self._stack_append(Type.int)
2✔
1075
        else:
1076
            opcode = OpcodeHelper.get_literal_push(-value)
2✔
1077
            if opcode is not None:
2✔
1078
                op_info: OpcodeInformation = OpcodeInfo.get_info(opcode)
2✔
1079
                self.__insert1(op_info)
2✔
1080
                self._stack_append(Type.int)
2✔
1081
                self.convert_operation(UnaryOp.Negative)
2✔
1082
            else:
1083
                opcode, data = OpcodeHelper.get_push_and_data(value)
2✔
1084
                op_info: OpcodeInformation = OpcodeInfo.get_info(opcode)
2✔
1085
                self.__insert1(op_info, data)
2✔
1086
                self._stack_append(Type.int)
2✔
1087

1088
    def convert_string_literal(self, value: str):
2✔
1089
        """
1090
        Converts an string literal value
1091

1092
        :param value: the value to be converted
1093
        """
1094
        array = bytes(value, constants.ENCODING)
2✔
1095
        self.insert_push_data(array)
2✔
1096
        self.convert_cast(Type.str)
2✔
1097

1098
    def convert_bool_literal(self, value: bool):
2✔
1099
        """
1100
        Converts an boolean literal value
1101

1102
        :param value: the value to be converted
1103
        """
1104
        if value:
2✔
1105
            self.__insert1(OpcodeInfo.PUSHT)
2✔
1106
        else:
1107
            self.__insert1(OpcodeInfo.PUSHF)
2✔
1108
        self._stack_append(Type.bool)
2✔
1109

1110
    def convert_sequence_literal(self, sequence: Sequence):
2✔
1111
        """
1112
        Converts a sequence value
1113

1114
        :param sequence: the value to be converted
1115
        """
1116
        if isinstance(sequence, tuple):
2✔
1117
            value_type = Type.tuple.build(sequence)
2✔
1118
        else:
1119
            value_type = Type.list.build(list(sequence))
2✔
1120

1121
        for inner_value in reversed(sequence):
2✔
1122
            self.convert_literal(inner_value)
2✔
1123

1124
        self.convert_new_array(len(sequence), value_type)
2✔
1125

1126
    def convert_dict_literal(self, dictionary: dict):
2✔
1127
        """
1128
        Converts a dict value
1129

1130
        :param dictionary: the value to be converted
1131
        """
1132
        value_type = Type.dict.build(dictionary)
2✔
1133
        self.convert_new_map(value_type)
2✔
1134

1135
        for key, value in dictionary.items():
2✔
1136
            self.duplicate_stack_top_item()
2✔
1137
            self.convert_literal(key)
2✔
1138
            value_start = self.convert_literal(value)
2✔
1139
            self.convert_set_item(value_start)
2✔
1140

1141
    def convert_byte_array(self, array: bytes):
2✔
1142
        """
1143
        Converts a byte value
1144

1145
        :param array: the value to be converted
1146
        """
1147
        self.insert_push_data(array)
2✔
1148
        self.convert_cast(Type.bytearray if isinstance(array, bytearray)
2✔
1149
                          else Type.bytes)
1150

1151
    def insert_push_data(self, data: bytes):
2✔
1152
        """
1153
        Inserts a push data value
1154

1155
        :param data: the value to be converted
1156
        """
1157
        data_len: int = len(data)
2✔
1158
        if data_len <= OpcodeInfo.PUSHDATA1.max_data_len:
2✔
1159
            op_info = OpcodeInfo.PUSHDATA1
2✔
1160
        elif data_len <= OpcodeInfo.PUSHDATA2.max_data_len:
×
1161
            op_info = OpcodeInfo.PUSHDATA2
×
1162
        else:
1163
            op_info = OpcodeInfo.PUSHDATA4
×
1164

1165
        data = Integer(data_len).to_byte_array(min_length=op_info.data_len) + data
2✔
1166
        self.__insert1(op_info, data)
2✔
1167
        self._stack_append(Type.str)  # push data pushes a ByteString value in the stack
2✔
1168

1169
    def insert_none(self):
2✔
1170
        """
1171
        Converts None literal
1172
        """
1173
        self.__insert1(OpcodeInfo.PUSHNULL)
2✔
1174
        self._stack_append(Type.none)
2✔
1175

1176
    def convert_cast(self, value_type: IType, is_internal: bool = False):
2✔
1177
        """
1178
        Converts casting types in Neo VM
1179
        """
1180
        stack_top_type: IType = self._stack[-1]
2✔
1181
        if (not value_type.is_generic
2✔
1182
                and not stack_top_type.is_generic
1183
                and value_type.stack_item is not Type.any.stack_item):
1184

1185
            if is_internal or value_type.stack_item != stack_top_type.stack_item:
2✔
1186
                # converts only if the stack types are different
1187
                self.__insert1(OpcodeInfo.CONVERT, value_type.stack_item)
2✔
1188

1189
            # but changes the value internally
1190
            self._stack_pop()
2✔
1191
            self._stack_append(value_type)
2✔
1192

1193
    def convert_new_map(self, map_type: IType):
2✔
1194
        """
1195
        Converts the creation of a new map
1196

1197
        :param map_type: the Neo Boa type of the map
1198
        """
1199
        self.__insert1(OpcodeInfo.NEWMAP)
2✔
1200
        self._stack_append(map_type)
2✔
1201

1202
    def convert_new_empty_array(self, length: int, array_type: IType):
2✔
1203
        """
1204
        Converts the creation of a new empty array
1205

1206
        :param length: the size of the new array
1207
        :param array_type: the Neo Boa type of the array
1208
        """
1209
        if length <= 0:
2✔
1210
            self.__insert1(OpcodeInfo.NEWARRAY0)
2✔
1211
        else:
1212
            self.convert_literal(length)
2✔
1213
            self._stack_pop()
2✔
1214
            self.__insert1(OpcodeInfo.NEWARRAY)
2✔
1215
        self._stack_append(array_type)
2✔
1216

1217
    def convert_new_array(self, length: int, array_type: IType = Type.list):
2✔
1218
        """
1219
        Converts the creation of a new array
1220

1221
        :param length: the size of the new array
1222
        :param array_type: the Neo Boa type of the array
1223
        """
1224
        if length <= 0:
2✔
1225
            self.convert_new_empty_array(length, array_type)
2✔
1226
        else:
1227
            self.convert_literal(length)
2✔
1228
            if array_type.stack_item is StackItemType.Struct:
2✔
1229
                self.__insert1(OpcodeInfo.PACKSTRUCT)
×
1230
            else:
1231
                self.__insert1(OpcodeInfo.PACK)
2✔
1232
            self._stack_pop()  # array size
2✔
1233
            for x in range(length):
2✔
1234
                self._stack_pop()
2✔
1235
            self._stack_append(array_type)
2✔
1236

1237
    def _set_array_item(self, value_start_address: int, check_for_negative_index: bool = True):
2✔
1238
        """
1239
        Converts the end of setting af a value in an array
1240
        """
1241
        index_type: IType = self._stack[-2]  # top: index
2✔
1242
        if index_type is Type.int and check_for_negative_index:
2✔
1243
            self.fix_negative_index(value_start_address)
2✔
1244

1245
    def convert_set_item(self, value_start_address: int, index_inserted_internally: bool = False):
2✔
1246
        """
1247
        Converts the end of setting af a value in an array
1248
        """
1249
        item_type: IType = self._stack[-3]  # top: index, 2nd-to-top: value, 3nd-to-top: array or map
2✔
1250
        if item_type.stack_item is not StackItemType.Map:
2✔
1251
            self._set_array_item(value_start_address, check_for_negative_index=not index_inserted_internally)
2✔
1252

1253
        self.__insert1(OpcodeInfo.SETITEM)
2✔
1254
        self._stack_pop()  # value
2✔
1255
        self._stack_pop()  # index
2✔
1256
        self._stack_pop()  # array or map
2✔
1257

1258
    def _get_array_item(self, check_for_negative_index: bool = True, test_is_negative_index=True):
2✔
1259
        """
1260
        Converts the end of get a value in an array
1261
        """
1262
        index_type: IType = self._stack[-1]  # top: index
2✔
1263
        if index_type is Type.int and check_for_negative_index:
2✔
1264
            self.fix_negative_index(test_is_negative=test_is_negative_index)
2✔
1265

1266
    def convert_get_item(self, index_inserted_internally: bool = False, index_is_positive=False, test_is_negative_index=True):
2✔
1267
        array_or_map_type: IType = self._stack[-2]  # second-to-top: array or map
2✔
1268
        if array_or_map_type.stack_item is not StackItemType.Map:
2✔
1269
            self._get_array_item(check_for_negative_index=not (index_inserted_internally or index_is_positive),
2✔
1270
                                 test_is_negative_index=test_is_negative_index)
1271

1272
        if array_or_map_type is Type.str:
2✔
1273
            self.convert_literal(1)  # length of substring
2✔
1274
            self.convert_get_substring(is_internal=index_inserted_internally or index_is_positive or not test_is_negative_index)
2✔
1275
        else:
1276
            self.__insert1(OpcodeInfo.PICKITEM)
2✔
1277
            self._stack_pop()
2✔
1278
            self._stack_pop()
2✔
1279
            if hasattr(array_or_map_type, 'value_type'):
2✔
1280
                new_stack_item = array_or_map_type.value_type
2✔
1281
            else:
1282
                new_stack_item = Type.any
2✔
1283
            self._stack_append(new_stack_item)
2✔
1284

1285
    def convert_get_substring(self, *, is_internal: bool = False, fix_result_type: bool = True):
2✔
1286
        """
1287
        Converts the end of get a substring
1288

1289
        :param is_internal: whether it was called when generating other implemented symbols
1290
        """
1291
        if not is_internal:
2✔
1292
            # if given substring size is negative, return empty string
1293
            self.duplicate_stack_top_item()
2✔
1294
            self.convert_literal(0)
2✔
1295
            self.convert_operation(BinaryOp.GtE)
2✔
1296

1297
            self._insert_jump(OpcodeInfo.JMPIF)
2✔
1298
            jmp_address = self.last_code_start_address
2✔
1299
            self.remove_stack_top_item()
2✔
1300
            self.convert_literal(0)
2✔
1301

1302
        self._stack_pop()  # length
2✔
1303
        self._stack_pop()  # start
2✔
1304
        original = self._stack_pop()  # original string
2✔
1305

1306
        self.__insert1(OpcodeInfo.SUBSTR)
2✔
1307
        if not is_internal:
2✔
1308
            self._update_jump(jmp_address, self.last_code_start_address)
2✔
1309
        self._stack_append(BufferType)  # substr returns a buffer instead of a bytestring
2✔
1310
        if fix_result_type:
2✔
1311
            self.convert_cast(original)
2✔
1312

1313
    def convert_get_array_slice(self, array: SequenceType):
2✔
1314
        """
1315
        Converts the end of get a substring
1316
        """
1317
        self.convert_new_empty_array(0, array)      # slice = []
2✔
1318
        self.duplicate_stack_item(3)                # index = slice_start
2✔
1319

1320
        start_jump = self.convert_begin_while()  # while index < slice_end
2✔
1321
        self.duplicate_stack_top_item()             # if index >= slice_start
2✔
1322
        self.duplicate_stack_item(5)
2✔
1323
        self.convert_operation(BinaryOp.GtE)
2✔
1324
        is_valid_index = self.convert_begin_if()
2✔
1325

1326
        self.duplicate_stack_item(2)                    # slice.append(array[index])
2✔
1327
        self.duplicate_stack_item(6)
2✔
1328
        self.duplicate_stack_item(3)
2✔
1329
        self.convert_get_item()
2✔
1330
        self.convert_builtin_method_call(Builtin.SequenceAppend.build(array))
2✔
1331
        self.convert_end_if(is_valid_index)
2✔
1332

1333
        self.__insert1(OpcodeInfo.INC)              # index += 1
2✔
1334

1335
        condition_address = VMCodeMapping.instance().bytecode_size
2✔
1336
        self.duplicate_stack_top_item()         # end while index < slice_end
2✔
1337
        self.duplicate_stack_item(4)
2✔
1338
        self.convert_operation(BinaryOp.Lt)
2✔
1339
        self.convert_end_while(start_jump, condition_address)
2✔
1340

1341
        self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
2✔
1342
        self.remove_stack_top_item()        # removes from the stack the arguments and the index
2✔
1343
        self.swap_reverse_stack_items(4)    # doesn't use CLEAR opcode because this would delete
2✔
1344
        self.remove_stack_top_item()        # data from external scopes
2✔
1345
        self.remove_stack_top_item()
2✔
1346
        self.remove_stack_top_item()
2✔
1347

1348
    def convert_get_sub_sequence(self):
2✔
1349
        """
1350
        Gets a slice of an array or ByteString
1351
        """
1352
        # top: length, index, array
1353
        if len(self._stack) > 2 and isinstance(self._stack[-3], SequenceType):
2✔
1354

1355
            if self._stack[-3].stack_item in (StackItemType.ByteString,
2✔
1356
                                              StackItemType.Buffer):
1357
                self.duplicate_stack_item(2)
2✔
1358
                self.convert_operation(BinaryOp.Sub)
2✔
1359
                self.convert_get_substring()
2✔
1360
            else:
1361
                array = self._stack[-3]
2✔
1362
                self.convert_get_array_slice(array)
2✔
1363

1364
    def convert_get_sequence_beginning(self):
2✔
1365
        """
1366
        Gets the beginning slice of an array or ByteString
1367
        """
1368
        if len(self._stack) > 1 and isinstance(self._stack[-2], SequenceType):
2✔
1369
            if self._stack[-2].stack_item in (StackItemType.ByteString,
2✔
1370
                                              StackItemType.Buffer):
1371
                self.__insert1(OpcodeInfo.LEFT)
2✔
1372
                self._stack_pop()  # length
2✔
1373
                original_type = self._stack_pop()  # original array
2✔
1374
                self._stack_append(BufferType)  # left returns a buffer instead of a bytestring
2✔
1375
                self.convert_cast(original_type)
2✔
1376
            else:
1377
                array = self._stack[-2]
2✔
1378

1379
                self.convert_literal(0)
2✔
1380
                self.swap_reverse_stack_items(2)
2✔
1381
                self.convert_get_array_slice(array)
2✔
1382

1383
    def convert_get_sequence_ending(self):
2✔
1384
        """
1385
        Gets the ending slice of an array or ByteString
1386
        """
1387
        # top: array, start_slice
1388
        if len(self._stack) > 1 and isinstance(self._stack[-2], SequenceType):
2✔
1389
            if self._stack[-2].stack_item in (StackItemType.ByteString,
2✔
1390
                                              StackItemType.Buffer):
1391
                self.duplicate_stack_item(2)
2✔
1392
                self.convert_builtin_method_call(Builtin.Len)
2✔
1393
                self.swap_reverse_stack_items(2)
2✔
1394
                self.convert_operation(BinaryOp.Sub)    # gets the amount of chars that should be taken after the index
2✔
1395
                self.__insert1(OpcodeInfo.RIGHT)
2✔
1396
                self._stack_pop()  # length
2✔
1397
                original_type = self._stack_pop()  # original array
2✔
1398
                self._stack_append(BufferType)     # right returns a buffer instead of a bytestring
2✔
1399
                self.convert_cast(original_type)
2✔
1400
            else:
1401
                array = self._stack[-2]
2✔
1402

1403
                self.duplicate_stack_item(2)
2✔
1404
                self.convert_builtin_method_call(Builtin.Len)
2✔
1405

1406
                self.convert_get_array_slice(array)
2✔
1407

1408
    def convert_copy(self):
2✔
1409
        if self._stack[-1].stack_item is StackItemType.Array:
2✔
1410
            self.__insert1(OpcodeInfo.UNPACK)
2✔
1411
            self.__insert1(OpcodeInfo.PACK)    # creates a new array with the values
2✔
1412

1413
    def convert_get_stride(self):
2✔
1414
        if len(self._stack) > 1 and isinstance(self._stack[-2], SequenceType):
2✔
1415
            if self._stack[-2].stack_item in (StackItemType.ByteString, StackItemType.Buffer):
2✔
1416
                self.convert_get_substring_stride()
2✔
1417
            else:
1418
                self.convert_get_array_stride()
2✔
1419

1420
    def convert_array_negative_stride(self):
2✔
1421
        """
1422
        Converts an array to its reverse, to be able to scroll through the reversed list
1423
        """
1424
        # The logic on this function only do variable[::-z]
1425

1426
        original = self._stack[-1]
2✔
1427
        self.convert_builtin_method_call(Builtin.Reversed)
2✔
1428
        self.convert_cast(ListType())
2✔
1429
        if isinstance(original, (StrType, BytesType)):        # if self was a string/bytes, then concat the values in the array
2✔
1430
            self.duplicate_stack_top_item()
2✔
1431
            self.convert_builtin_method_call(Builtin.Len)       # index = len(array) - 1
2✔
1432
            self.convert_literal('')                            # string = ''
2✔
1433

1434
            start_jump = self.convert_begin_while()
2✔
1435
            self.duplicate_stack_item(3)
2✔
1436
            self.duplicate_stack_item(3)
2✔
1437
            str_type = self._stack[-3]
2✔
1438
            self.__insert1(OpcodeInfo.PICKITEM)
2✔
1439
            self._stack_pop()
2✔
1440
            self._stack_pop()
2✔
1441
            self._stack_append(str_type)
2✔
1442
            self.swap_reverse_stack_items(2)
2✔
1443
            self.convert_operation(BinaryOp.Concat)             # string = string + array[index]
2✔
1444

1445
            condition_address = VMCodeMapping.instance().bytecode_size
2✔
1446
            self.swap_reverse_stack_items(2)
2✔
1447
            self.__insert1(OpcodeInfo.DEC)                      # index--
2✔
1448
            self.swap_reverse_stack_items(2)
2✔
1449
            self.duplicate_stack_item(2)
2✔
1450
            self.convert_literal(0)
2✔
1451
            self.convert_operation(BinaryOp.GtE)                # if index <= 0, stop loop
2✔
1452
            self.convert_end_while(start_jump, condition_address)
2✔
1453
            self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
2✔
1454

1455
            # remove auxiliary values
1456
            self.swap_reverse_stack_items(3)
2✔
1457
            self.remove_stack_top_item()
2✔
1458
            self.remove_stack_top_item()
2✔
1459

1460
    def convert_get_substring_stride(self):
2✔
1461
        # initializing auxiliary variables
1462
        self.duplicate_stack_item(2)
2✔
1463
        self.convert_builtin_method_call(Builtin.Len)
2✔
1464
        self.convert_literal(0)                         # index = 0
2✔
1465
        self.convert_literal('')                        # substr = ''
2✔
1466

1467
        # logic verifying if substr[index] should be concatenated or not
1468
        start_jump = self.convert_begin_while()
2✔
1469
        self.duplicate_stack_item(2)
2✔
1470
        self.duplicate_stack_item(5)
2✔
1471
        self.convert_operation(BinaryOp.Mod)
2✔
1472
        self.convert_literal(0)
2✔
1473
        self.convert_operation(BinaryOp.NumEq)
2✔
1474
        is_mod_0 = self.convert_begin_if()              # if index % stride == 0, then concatenate it
2✔
1475

1476
        # concatenating substr[index] with substr
1477
        self.duplicate_stack_item(5)
2✔
1478
        self.duplicate_stack_item(3)
2✔
1479
        self.convert_literal(1)
2✔
1480
        self._stack_pop()  # length
2✔
1481
        self._stack_pop()  # start
2✔
1482
        str_type = self._stack_pop()
2✔
1483
        self.__insert1(OpcodeInfo.SUBSTR)
2✔
1484
        self._stack_append(BufferType)                  # SUBSTR returns a buffer instead of a bytestring
2✔
1485
        self.convert_cast(str_type)
2✔
1486
        self.convert_operation(BinaryOp.Concat)         # substr = substr + string[index]
2✔
1487
        self.convert_end_if(is_mod_0)
2✔
1488

1489
        # increment the index by 1
1490
        self.swap_reverse_stack_items(2)
2✔
1491
        self.__insert1(OpcodeInfo.INC)                  # index++
2✔
1492
        self.swap_reverse_stack_items(2)
2✔
1493

1494
        # verifying if it should still be in the while
1495
        condition_address = VMCodeMapping.instance().bytecode_size
2✔
1496
        self.duplicate_stack_item(2)
2✔
1497
        self.duplicate_stack_item(4)
2✔
1498
        self.convert_operation(BinaryOp.Lt)             # stop the loop when index >= len(str)
2✔
1499
        self.convert_end_while(start_jump, condition_address)
2✔
1500
        self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
2✔
1501

1502
        # removing auxiliary values
1503
        self.swap_reverse_stack_items(5)
2✔
1504
        self.remove_stack_top_item()
2✔
1505
        self.remove_stack_top_item()
2✔
1506
        self.remove_stack_top_item()
2✔
1507
        self.remove_stack_top_item()
2✔
1508

1509
    def convert_get_array_stride(self):
2✔
1510
        # initializing auxiliary variable
1511
        self.duplicate_stack_item(2)
2✔
1512
        self.convert_builtin_method_call(Builtin.Len)
2✔
1513
        self.__insert1(OpcodeInfo.DEC)                      # index = len(array) - 1
2✔
1514

1515
        # logic verifying if array[index] should be removed or not
1516
        start_jump = self.convert_begin_while()
2✔
1517
        self.duplicate_stack_item(2)
2✔
1518
        self.duplicate_stack_item(2)
2✔
1519
        self.swap_reverse_stack_items(2)
2✔
1520
        self.convert_operation(BinaryOp.Mod)
2✔
1521
        self.convert_literal(0)
2✔
1522
        self.convert_operation(BinaryOp.NumNotEq)
2✔
1523
        is_not_mod_0 = self.convert_begin_if()              # if index % stride != 0, then remove it
2✔
1524

1525
        # removing element from array
1526
        self.duplicate_stack_item(3)
2✔
1527
        self.duplicate_stack_item(2)
2✔
1528
        self.__insert1(OpcodeInfo.REMOVE)                   # array.pop(index)
2✔
1529
        self._stack_pop()
2✔
1530
        self._stack_pop()
2✔
1531
        self.convert_end_if(is_not_mod_0)
2✔
1532

1533
        # decrement 1 from index
1534
        self.__insert1(OpcodeInfo.DEC)
2✔
1535

1536
        # verifying if it should still be in the while
1537
        condition_address = VMCodeMapping.instance().bytecode_size
2✔
1538
        self.duplicate_stack_top_item()
2✔
1539
        self.__insert1(OpcodeInfo.SIGN)
2✔
1540
        self.convert_literal(-1)
2✔
1541
        self.convert_operation(BinaryOp.NumNotEq)       # stop the loop when index < 0
2✔
1542
        self.convert_end_while(start_jump, condition_address)
2✔
1543
        self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
2✔
1544

1545
        # removing auxiliary values
1546
        self.remove_stack_top_item()                    # removed index from stack
2✔
1547
        self.remove_stack_top_item()                    # removed stride from stack
2✔
1548

1549
    def convert_starred_variable(self):
2✔
1550
        top_stack_item = self._stack[-1].stack_item
2✔
1551
        if top_stack_item is StackItemType.Array:
2✔
1552
            self.convert_copy()
2✔
1553
        elif top_stack_item is StackItemType.Map:
×
1554
            self.convert_builtin_method_call(Builtin.DictKeys)
×
1555
        else:
1556
            return
×
1557

1558
        self.convert_cast(Type.tuple)
2✔
1559

1560
    def convert_load_symbol(self, symbol_id: str, params_addresses: list[int] = None, is_internal: bool = False,
2✔
1561
                            class_type: UserClass | None = None):
1562
        """
1563
        Converts the load of a symbol
1564

1565
        :param symbol_id: the symbol identifier
1566
        :param params_addresses: a list with each function arguments' first addresses
1567
        """
1568
        another_symbol_id, symbol = self.get_symbol(symbol_id, is_internal=is_internal)
2✔
1569

1570
        if class_type is not None and symbol_id in class_type.symbols:
2✔
1571
            symbol = class_type.symbols[symbol_id]
2✔
1572

1573
        if symbol is not Type.none:
2✔
1574
            if isinstance(symbol, Property):
2✔
1575
                symbol = symbol.getter
2✔
1576
                params_addresses = []
2✔
1577
            elif isinstance(symbol, ClassType) and params_addresses is not None:
2✔
1578
                symbol = symbol.constructor_method()
2✔
1579

1580
            if not params_addresses:
2✔
1581
                params_addresses = []
2✔
1582

1583
            if isinstance(symbol, Variable):
2✔
1584
                symbol_id = another_symbol_id
2✔
1585
                self.convert_load_variable(symbol_id, symbol, class_type)
2✔
1586
            elif isinstance(symbol, IBuiltinMethod) and symbol.body is None:
2✔
1587
                self.convert_builtin_method_call(symbol, params_addresses)
2✔
1588
            elif isinstance(symbol, Event):
2✔
1589
                self.convert_event_call(symbol)
2✔
1590
            elif isinstance(symbol, Method):
2✔
1591
                self.convert_method_call(symbol, len(params_addresses))
2✔
1592
            elif isinstance(symbol, UserClass):
2✔
1593
                self.convert_class_symbol(symbol, symbol_id)
2✔
1594

1595
    def convert_load_class_variable(self, class_type: ClassType, var_id: str, is_internal: bool = False):
2✔
1596
        variable_list = (class_type._all_variables
2✔
1597
                         if hasattr(class_type, '_all_variables') and is_internal
1598
                         else class_type.variables)
1599

1600
        if var_id in variable_list:
2✔
1601
            var = variable_list[var_id]
2✔
1602
            index = list(variable_list).index(var_id)
2✔
1603
            self.convert_literal(index)
2✔
1604
            self.convert_get_item(index_inserted_internally=True)
2✔
1605
            self._stack_pop()  # pop class type
2✔
1606
            self._stack_append(var.type)  # push variable type
2✔
1607

1608
    def convert_load_variable(self, var_id: str, var: Variable, class_type: UserClass | None = None):
2✔
1609
        """
1610
        Converts the assignment of a variable
1611

1612
        :param var_id: the value to be converted
1613
        :param var: the actual variable to be loaded
1614
        """
1615
        index, local, is_arg = self._get_variable_info(var_id)
2✔
1616
        if index >= 0:
2✔
1617
            opcode = OpcodeHelper.get_load(index, local, is_arg)
2✔
1618
            op_info = OpcodeInfo.get_info(opcode)
2✔
1619

1620
            if op_info.data_len > 0:
2✔
1621
                self.__insert1(op_info, Integer(index).to_byte_array())
2✔
1622
            else:
1623
                self.__insert1(op_info)
2✔
1624
            self._stack_append(var.type)
2✔
1625

1626
        elif hasattr(var.type, 'get_value'):
2✔
1627
            # the variable is a type constant
1628
            value = var.type.get_value(var_id.split(constants.ATTRIBUTE_NAME_SEPARATOR)[-1])
2✔
1629
            if value is not None:
2✔
1630
                self.convert_literal(value)
2✔
1631

1632
        elif var_id in self._globals:
2✔
1633
            another_var_id, var = self.get_symbol(var_id)
2✔
1634
            storage_key = codegenerator.get_storage_key_for_variable(var)
2✔
1635
            self._convert_builtin_storage_get_or_put(True, storage_key)
2✔
1636

1637
        elif var.is_global:
2✔
1638
            if not self.store_constant_variable(var):
2✔
1639
                self.convert_literal(var._first_assign_value)
2✔
1640

1641
        elif class_type:
2✔
1642
            self.convert_load_class_variable(class_type, var_id)
2✔
1643

1644
    def convert_store_variable(self, var_id: str, value_start_address: int = None, user_class: UserClass = None):
2✔
1645
        """
1646
        Converts the assignment of a variable
1647

1648
        :param var_id: the value to be converted
1649
        """
1650
        inner_index = None
2✔
1651
        index, local, is_arg = self._get_variable_info(var_id)
2✔
1652

1653
        if user_class is None and len(self._stack) > 1 and isinstance(self._stack[-2], UserClass):
2✔
1654
            user_class = self._stack[-2]
2✔
1655

1656
        if isinstance(user_class, UserClass) and var_id in user_class.variables:
2✔
1657
            index, local, is_arg = self._get_variable_info(user_class.identifier)
2✔
1658
            inner_index = list(user_class.variables).index(var_id)
2✔
1659

1660
        if isinstance(inner_index, int):
2✔
1661
            # it's a variable from a class
1662
            self.convert_literal(inner_index)
2✔
1663
            index_address = self.bytecode_size
2✔
1664

1665
            if var_id in user_class.class_variables:
2✔
1666
                # it's a class variable
1667
                self.convert_load_variable(user_class.identifier, Variable(user_class))
2✔
1668
                no_stack_items_to_swap = 3
2✔
1669
            else:
1670
                no_stack_items_to_swap = 2
2✔
1671

1672
            self.swap_reverse_stack_items(no_stack_items_to_swap)
2✔
1673
            self.convert_set_item(index_address, index_inserted_internally=True)
2✔
1674
            return
2✔
1675

1676
        if index >= 0:
2✔
1677
            opcode = OpcodeHelper.get_store(index, local, is_arg)
2✔
1678
            if opcode is not None:
2✔
1679
                op_info = OpcodeInfo.get_info(opcode)
2✔
1680

1681
                if op_info.data_len > 0:
2✔
1682
                    self.__insert1(op_info, Integer(index).to_byte_array())
2✔
1683
                else:
1684
                    self.__insert1(op_info)
2✔
1685
                stored_type = self._stack_pop()
2✔
1686

1687
                from boa3.internal.analyser.model.optimizer import UndefinedType
2✔
1688
                if (var_id in self._current_scope.symbols or
2✔
1689
                        (var_id in self._locals and self._current_method.locals[var_id].type is UndefinedType)):
1690
                    another_symbol_id, symbol = self.get_symbol(var_id)
2✔
1691
                    if isinstance(symbol, Variable):
2✔
1692
                        var = symbol.copy()
2✔
1693
                        var.set_type(stored_type)
2✔
1694
                        self._current_scope.include_symbol(var_id, var)
2✔
1695

1696
        elif var_id in self._globals:
2✔
1697
            another_var_id, var = self.get_symbol(var_id)
2✔
1698
            storage_key = codegenerator.get_storage_key_for_variable(var)
2✔
1699
            if value_start_address is None:
2✔
1700
                value_start_address = self.bytecode_size
×
1701
            self._convert_builtin_storage_get_or_put(False, storage_key, value_start_address)
2✔
1702

1703
    def _convert_builtin_storage_get_or_put(self, is_get: bool, storage_key: bytes, arg_address: int = None):
2✔
1704
        addresses = [arg_address] if arg_address is not None else [self.bytecode_size]
2✔
1705
        if not is_get:
2✔
1706
            # must serialized before storing the value
1707
            self.convert_builtin_method_call(Interop.Serialize, addresses)
2✔
1708

1709
        self.convert_literal(storage_key)
2✔
1710
        self.convert_builtin_method_call(Interop.StorageGetContext)
2✔
1711

1712
        builtin_method = Interop.StorageGet if is_get else Interop.StoragePut
2✔
1713
        self.convert_builtin_method_call(builtin_method)
2✔
1714

1715
        if is_get:
2✔
1716
            # once the value is retrieved, it must be deserialized
1717
            self.convert_builtin_method_call(Interop.Deserialize, addresses)
2✔
1718

1719
    def _get_variable_info(self, var_id: str) -> tuple[int, bool, bool]:
2✔
1720
        """
1721
        Gets the necessary information about the variable to get the correct opcode
1722

1723
        :param var_id: the name id of the
1724
        :return: returns the index of the variable in its scope and two boolean variables for representing the
1725
        variable scope:
1726
            `local` is True if it is a local variable and
1727
            `is_arg` is True only if the variable is a parameter of the function.
1728
        If the variable is not found, returns (-1, False, False)
1729
        """
1730
        is_arg: bool = False
2✔
1731
        local: bool = False
2✔
1732
        scope = None
2✔
1733

1734
        if var_id in self._args:
2✔
1735
            is_arg: bool = True
2✔
1736
            local: bool = True
2✔
1737
            scope = self._args
2✔
1738
        elif var_id in self._locals:
2✔
1739
            is_arg = False
2✔
1740
            local: bool = True
2✔
1741
            scope = self._locals
2✔
1742
        elif var_id in self._statics:
2✔
1743
            scope = self._statics
2✔
1744

1745
        if scope is not None:
2✔
1746
            index: int = scope.index(var_id) if var_id in scope else -1
2✔
1747
        else:
1748
            index = -1
2✔
1749

1750
        return index, local, is_arg
2✔
1751

1752
    def convert_builtin_method_call(self, function: IBuiltinMethod, args_address: list[int] = None, is_internal: bool = False):
2✔
1753
        """
1754
        Converts a builtin method function call
1755

1756
        :param function: the function to be converted
1757
        :param args_address: a list with each function arguments' first addresses
1758
        :param is_internal: whether it was called when generating other implemented symbols
1759
        """
1760
        stack_before = len(self._stack)
2✔
1761
        if args_address is None:
2✔
1762
            args_address = []
2✔
1763
        store_opcode: OpcodeInformation = None
2✔
1764
        store_data: bytes = b''
2✔
1765

1766
        if function.pack_arguments:
2✔
1767
            self.convert_new_array(len(args_address))
×
1768

1769
        if function.stores_on_slot and 0 < len(function.args) <= len(args_address):
2✔
1770
            address = args_address[-len(function.args)]
2✔
1771
            load_instr = VMCodeMapping.instance().code_map[address]
2✔
1772
            if OpcodeHelper.is_load_slot(load_instr.opcode):
2✔
1773
                store: Opcode = OpcodeHelper.get_store_from_load(load_instr.opcode)
2✔
1774
                store_opcode = OpcodeInfo.get_info(store)
2✔
1775
                store_data = load_instr.data
2✔
1776

1777
        fix_negatives = function.validate_negative_arguments()
2✔
1778
        if len(fix_negatives) > 0:
2✔
1779
            args_end_addresses = args_address[1:]
2✔
1780
            args_end_addresses.append(self.bytecode_size)
2✔
1781

1782
            if function.push_self_first():
2✔
1783
                addresses = args_end_addresses[:1] + list(reversed(args_end_addresses[1:]))
2✔
1784
            else:
1785
                addresses = list(reversed(args_end_addresses))
×
1786

1787
            for arg in sorted(fix_negatives, reverse=True):
2✔
1788
                if len(addresses) > arg:
2✔
1789
                    self.fix_negative_index(addresses[arg])
2✔
1790

1791
        self._convert_builtin_call(function, previous_stack_size=stack_before, is_internal=is_internal)
2✔
1792

1793
        if store_opcode is not None:
2✔
1794
            self._insert_jump(OpcodeInfo.JMP)
2✔
1795
            self._update_codes_without_target_to_next(self.last_code_start_address)
2✔
1796
            jump = self.last_code_start_address
2✔
1797
            self.__insert1(store_opcode, store_data)
2✔
1798
            self._update_jump(jump, VMCodeMapping.instance().bytecode_size)
2✔
1799

1800
    def _convert_builtin_call(self, builtin: IBuiltinCallable, previous_stack_size: int = None, is_internal: bool = False):
2✔
1801
        if not isinstance(previous_stack_size, int):
2✔
1802
            previous_stack_size = len(self._stack)
2✔
1803
        elif previous_stack_size < 0:
2✔
1804
            previous_stack_size = 0
×
1805

1806
        size_before_generating = self.bytecode_size
2✔
1807
        if is_internal:
2✔
1808
            builtin.generate_internal_opcodes(self)
2✔
1809
        else:
1810
            builtin.generate_opcodes(self)
2✔
1811

1812
        if isinstance(builtin, IBuiltinMethod):
2✔
1813
            if is_internal and hasattr(builtin, 'internal_call_args'):
2✔
1814
                expected_stack_after = previous_stack_size - builtin.internal_call_args
2✔
1815
            else:
1816
                expected_stack_after = previous_stack_size - builtin.args_on_stack
2✔
1817
        else:
1818
            expected_stack_after = previous_stack_size - len(builtin.args)
2✔
1819

1820
        if expected_stack_after < 0:
2✔
1821
            expected_stack_after = 0
×
1822

1823
        while expected_stack_after < len(self._stack):
2✔
1824
            self._stack_pop()
2✔
1825
        if builtin.return_type not in (None, Type.none):
2✔
1826
            self._stack_append(builtin.return_type)
2✔
1827

1828
    def convert_method_call(self, function: Method, num_args: int):
2✔
1829
        """
1830
        Converts a method function call
1831

1832
        :param function: the function to be converted
1833
        """
1834
        if function.is_init:
2✔
1835
            if num_args == len(function.args):
2✔
1836
                self.remove_stack_top_item()
×
1837
                num_args -= 1
×
1838

1839
            if num_args == len(function.args) - 1:
2✔
1840
                # if this method is a constructor and only the self argument is missing
1841
                function_result = function.type
2✔
1842
                size = len(function_result.variables) if isinstance(function_result, UserClass) else 0
2✔
1843
                if self.stack_size < 1 or not self._stack[-1].is_type_of(function_result):
2✔
1844
                    self.convert_new_empty_array(size, function_result)
×
1845

1846
        if isinstance(function.origin_class, ContractInterfaceClass):
2✔
1847
            if function.external_name is not None:
2✔
1848
                function_id = function.external_name
2✔
1849
            else:
1850
                function_id = next((symbol_id
2✔
1851
                                    for symbol_id, symbol in function.origin_class.symbols.items()
1852
                                    if symbol is function),
1853
                                   None)
1854
            self._add_to_metadata_permissions(function.origin_class, function_id)
2✔
1855

1856
            if isinstance(function_id, str):
2✔
1857
                self.convert_new_array(len(function.args))
2✔
1858
                self.convert_literal(Interop.CallFlagsType.default_value)
2✔
1859
                self.convert_literal(function_id)
2✔
1860
                self.convert_literal(function.origin_class.contract_hash.to_array())
2✔
1861
                self.convert_builtin_method_call(Interop.CallContract)
2✔
1862
                self._stack_pop()  # remove call contract 'any' result from the stack
2✔
1863
                self._stack_append(function.return_type)    # add the return type on the stack even if it is None
2✔
1864

1865
        else:
1866
            from boa3.internal.neo.vm.CallCode import CallCode
2✔
1867
            call_code = CallCode(function)
2✔
1868
            self.__insert_code(call_code)
2✔
1869
            self._update_codes_with_target(call_code)
2✔
1870

1871
            for arg in range(num_args):
2✔
1872
                self._stack_pop()
2✔
1873
            if function.is_init:
2✔
1874
                self._stack_pop()  # pop duplicated result if it's init
2✔
1875

1876
            if function.return_type is not Type.none:
2✔
1877
                self._stack_append(function.return_type)
2✔
1878

1879
    def convert_method_token_call(self, method_token_id: int):
2✔
1880
        """
1881
        Converts a method token call
1882
        """
1883
        if method_token_id >= 0:
2✔
1884
            method_token = VMCodeMapping.instance().get_method_token(method_token_id)
2✔
1885
            if method_token is not None:
2✔
1886
                opcode_info = OpcodeInfo.CALLT
2✔
1887
                self.__insert1(opcode_info, Integer(method_token_id).to_byte_array(min_length=opcode_info.data_len))
2✔
1888

1889
    def convert_event_call(self, event: Event):
2✔
1890
        """
1891
        Converts an event call
1892

1893
        :param event_id: called event identifier
1894
        :param event: called event
1895
        """
1896
        self.convert_new_array(len(event.args_to_generate), Type.list)
2✔
1897
        if event.generate_name:
2✔
1898
            self.convert_literal(event.name)
2✔
1899
        else:
1900
            self.swap_reverse_stack_items(2)
2✔
1901

1902
        from boa3.internal.model.builtin.interop.interop import Interop
2✔
1903
        self._convert_builtin_call(Interop.Notify, is_internal=True)
2✔
1904

1905
    def convert_class_symbol(self, class_type: ClassType, symbol_id: str, load: bool = True) -> int | None:
2✔
1906
        """
1907
        Converts an class symbol
1908

1909
        :param class_type:
1910
        :param symbol_id:
1911
        :param load:
1912
        """
1913
        method: Method
1914
        is_safe_to_convert = False
2✔
1915

1916
        if symbol_id in class_type.variables:
2✔
1917
            return self.convert_class_variable(class_type, symbol_id, load)
2✔
1918
        elif symbol_id in class_type.properties:
2✔
1919
            symbol = class_type.properties[symbol_id]
2✔
1920
            is_safe_to_convert = True
2✔
1921
            method = symbol.getter if load else symbol.setter
2✔
1922
        elif symbol_id in class_type.instance_methods:
2✔
1923
            method = class_type.instance_methods[symbol_id]
2✔
1924
        elif symbol_id in class_type.class_methods:
2✔
1925
            method = class_type.class_methods[symbol_id]
2✔
1926
        elif isinstance(class_type, UserClass):
2✔
1927
            return self.convert_user_class(class_type, symbol_id)
2✔
1928
        else:
1929
            return
×
1930

1931
        if isinstance(method, IBuiltinMethod):
2✔
1932
            if not is_safe_to_convert:
2✔
1933
                is_safe_to_convert = len(method.args) == 0
2✔
1934

1935
            if is_safe_to_convert:
2✔
1936
                self.convert_builtin_method_call(method)
2✔
1937
        else:
1938
            self.convert_method_call(method, 0)
×
1939
        return symbol_id
2✔
1940

1941
    def convert_user_class(self, class_type: UserClass, symbol_id: str) -> int | None:
2✔
1942
        """
1943
        Converts an class symbol
1944

1945
        :param class_type:
1946
        :param symbol_id:
1947
        """
1948
        start_address = self.bytecode_size
2✔
1949

1950
        if symbol_id in self._statics:
2✔
1951
            self.convert_load_variable(symbol_id, Variable(class_type))
2✔
1952
        else:
1953
            # TODO: change to create an array with the class variables' default values when they are implemented #2kq1vgn
1954
            self.convert_new_empty_array(len(class_type.class_variables), class_type)
2✔
1955

1956
        return start_address
2✔
1957

1958
    def convert_class_variable(self, class_type: ClassType, symbol_id: str, load: bool = True):
2✔
1959
        """
1960
        Converts an class variable
1961

1962
        :param class_type:
1963
        :param symbol_id:
1964
        :param load:
1965
        """
1966
        if symbol_id in class_type.variables:
2✔
1967
            index = list(class_type.variables).index(symbol_id)
2✔
1968

1969
            if load:
2✔
1970
                self.convert_literal(index)
2✔
1971
                self.convert_get_item(index_inserted_internally=True)
2✔
1972

1973
                symbol_type = class_type.variables[symbol_id].type
2✔
1974
                if self._stack[-1] != symbol_type:
2✔
1975
                    self._stack_pop()
2✔
1976
                    self._stack_append(symbol_type)
2✔
1977

1978
            return index
2✔
1979

1980
    def convert_operation(self, operation: IOperation, is_internal: bool = False):
2✔
1981
        """
1982
        Converts an operation
1983

1984
        :param operation: the operation that will be converted
1985
        :param is_internal: whether it was called when generating other implemented symbols
1986
        """
1987
        stack_before = len(self._stack)
2✔
1988
        if is_internal:
2✔
1989
            operation.generate_internal_opcodes(self)
2✔
1990
        else:
1991
            operation.generate_opcodes(self)
2✔
1992

1993
        expected_stack_after = stack_before - operation.op_on_stack
2✔
1994
        if expected_stack_after < 0:
2✔
1995
            expected_stack_after = 0
×
1996

1997
        while expected_stack_after < len(self._stack):
2✔
1998
            self._stack_pop()
2✔
1999
        self._stack_append(operation.result)
2✔
2000

2001
    def convert_assert(self, has_message: bool = False):
2✔
2002

2003
        if has_message:
2✔
2004
            asserted_type = self._stack[-2] if len(self._stack) > 1 else Type.any
2✔
2005
        else:
2006
            asserted_type = self._stack[-1] if len(self._stack) > 0 else Type.any
2✔
2007

2008
        if not isinstance(asserted_type, PrimitiveType):
2✔
2009
            if has_message:
2✔
2010
                self.swap_reverse_stack_items(2)
×
2011

2012
            len_pos = VMCodeMapping.instance().bytecode_size
2✔
2013
            # if the value is an array, a map or a struct, asserts it is not empty
2014
            self.convert_builtin_method_call(Builtin.Len)
2✔
2015
            len_code = VMCodeMapping.instance().code_map[len_pos]
2✔
2016

2017
            if asserted_type is Type.any:
2✔
2018
                # need to check in runtime
2019
                self.duplicate_stack_top_item()
2✔
2020
                self.insert_type_check(StackItemType.Array)
2✔
2021
                self._insert_jump(OpcodeInfo.JMPIF, len_code)
2✔
2022

2023
                self.duplicate_stack_top_item()
2✔
2024
                self.insert_type_check(StackItemType.Map)
2✔
2025
                self._insert_jump(OpcodeInfo.JMPIF, len_code)
2✔
2026

2027
                self.duplicate_stack_top_item()
2✔
2028
                self.insert_type_check(StackItemType.Struct)
2✔
2029
                self._insert_jump(OpcodeInfo.JMPIFNOT, 2)
2✔
2030

2031
                VMCodeMapping.instance().move_to_end(len_pos, len_pos)
2✔
2032
            if has_message:
2✔
2033
                self.swap_reverse_stack_items(2)
×
2034

2035
        self.__insert1(OpcodeInfo.ASSERT if not has_message else OpcodeInfo.ASSERTMSG)
2✔
2036
        if has_message and len(self._stack) > 1:
2✔
2037
            # pop message
2038
            self._stack_pop()
2✔
2039

2040
        if len(self._stack) > 0:
2✔
2041
            # pop assert test
2042
            self._stack_pop()
2✔
2043

2044
    def convert_abort(self, has_message: bool = False, *, is_internal: bool = False):
2✔
2045
        if not is_internal:
2✔
2046
            abort_msg_type = self._stack[-1] if len(self._stack) > 0 else Type.none
2✔
2047
            if has_message and Type.none.is_type_of(abort_msg_type):
2✔
2048
                # do not use ABORTMSG if we can detect the msg is None
2049
                has_message = not has_message
×
2050
                self._stack_pop()
×
2051

2052
        if not has_message:
2✔
2053
            self.__insert1(OpcodeInfo.ABORT)
×
2054
        else:
2055
            self.__insert1(OpcodeInfo.ABORTMSG)
2✔
2056
            self._stack_pop()
2✔
2057

2058
    def convert_new_exception(self, exception_args_len: int = 0):
2✔
2059
        if exception_args_len == 0 or len(self._stack) == 0:
2✔
2060
            self.convert_literal(Builtin.Exception.default_message)
2✔
2061

2062
        if exception_args_len > 1:
2✔
2063
            self.convert_new_array(exception_args_len)
×
2064

2065
        self._stack_pop()
2✔
2066
        self._stack_append(Type.exception)
2✔
2067

2068
    def convert_raise_exception(self):
2✔
2069
        if len(self._stack) == 0:
2✔
2070
            self.convert_literal(Builtin.Exception.default_message)
×
2071

2072
        self._stack_pop()
2✔
2073
        self.__insert1(OpcodeInfo.THROW)
2✔
2074

2075
    def insert_opcode(self, opcode: Opcode, data: bytes = None,
2✔
2076
                      add_to_stack: list[IType] = None, pop_from_stack: bool = False):
2077
        """
2078
        Inserts one opcode into the bytecode. Used to generate built-in symbols.
2079

2080
        :param opcode: info of the opcode  that will be inserted
2081
        :param data: data of the opcode, if needed
2082
        :param add_to_stack: expected data to be included on stack, if needed
2083
        :param pop_from_stack: if needs to update stack given opcode stack items
2084
        """
2085
        op_info = OpcodeInfo.get_info(opcode)
2✔
2086
        if op_info is not None:
2✔
2087
            self.__insert1(op_info, data)
2✔
2088
            if pop_from_stack:
2✔
2089
                for _ in range(op_info.stack_items):
2✔
2090
                    self._stack_pop()
2✔
2091

2092
        if isinstance(add_to_stack, list):
2✔
2093
            for stack_item in add_to_stack:
2✔
2094
                self._stack_append(stack_item)
2✔
2095

2096
    def insert_type_check(self, type_to_check: StackItemType | None):
2✔
2097
        if isinstance(type_to_check, StackItemType):
2✔
2098
            self.__insert1(OpcodeInfo.ISTYPE, type_to_check)
2✔
2099
        else:
2100
            self.__insert1(OpcodeInfo.ISNULL)
2✔
2101
        self._stack_pop()
2✔
2102
        self._stack_append(Type.bool)
2✔
2103

2104
    def __insert1(self, op_info: OpcodeInformation, data: bytes = None):
2✔
2105
        """
2106
        Inserts one opcode into the bytecode
2107

2108
        :param op_info: info of the opcode  that will be inserted
2109
        :param data: data of the opcode, if needed
2110
        """
2111
        vm_code = VMCode(op_info, data)
2✔
2112

2113
        if OpcodeHelper.has_target(op_info.opcode):
2✔
2114
            data = vm_code.raw_data
2✔
2115
            relative_address: int = Integer.from_bytes(data, signed=True)
2✔
2116
            actual_address = VMCodeMapping.instance().bytecode_size + relative_address
2✔
2117
            if (self._can_append_target
2✔
2118
                    and relative_address != 0
2119
                    and actual_address in VMCodeMapping.instance().code_map):
2120
                vm_code.set_target(VMCodeMapping.instance().code_map[actual_address])
2✔
2121
            else:
2122
                self._include_missing_target(vm_code, actual_address)
2✔
2123

2124
        self.__insert_code(vm_code)
2✔
2125
        self._update_codes_with_target(vm_code)
2✔
2126

2127
    def __insert_code(self, vm_code: VMCode):
2✔
2128
        """
2129
        Inserts one vmcode into the bytecode
2130

2131
        :param vm_code: the opcode that will be inserted
2132
        """
2133
        VMCodeMapping.instance().insert_code(vm_code)
2✔
2134

2135
    def _include_missing_target(self, vmcode: VMCode, target_address: int = 0):
2✔
2136
        """
2137
        Includes a instruction which parameter is another instruction that wasn't converted yet
2138

2139
        :param vmcode: instruction with incomplete parameter
2140
        :param target_address: target instruction expected address
2141
        :return:
2142
        """
2143
        if OpcodeHelper.has_target(vmcode.opcode):
2✔
2144
            if target_address == VMCodeMapping.instance().bytecode_size:
2✔
2145
                target_address = None
2✔
2146
            else:
2147
                self._remove_missing_target(vmcode)
2✔
2148

2149
            if target_address not in self._missing_target:
2✔
2150
                self._missing_target[target_address] = []
2✔
2151
            if vmcode not in self._missing_target[target_address]:
2✔
2152
                self._missing_target[target_address].append(vmcode)
2✔
2153

2154
    def _remove_missing_target(self, vmcode: VMCode):
2✔
2155
        """
2156
        Removes a instruction from the missing target list
2157

2158
        :param vmcode: instruction with incomplete parameter
2159
        :return:
2160
        """
2161
        if OpcodeHelper.has_target(vmcode.opcode):
2✔
2162
            for target_address, opcodes in self._missing_target.copy().items():
2✔
2163
                if vmcode in opcodes:
2✔
2164
                    opcodes.remove(vmcode)
2✔
2165
                    if len(opcodes) == 0:
2✔
2166
                        self._missing_target.pop(target_address)
2✔
2167
                    break
2✔
2168

2169
    def _check_codes_with_target(self) -> bool:
2✔
2170
        """
2171
        Verifies if there are any instructions targeting positions not included yet.
2172
        """
2173
        instance = VMCodeMapping.instance()
2✔
2174
        current_bytecode_size = instance.bytecode_size
2✔
2175
        for target_address, codes in list(self._missing_target.items()):
2✔
2176
            if target_address is not None and target_address >= current_bytecode_size:
2✔
2177
                return True
×
2178

2179
        if None in self._missing_target:
2✔
2180
            for code in self._missing_target[None]:
2✔
2181
                if OpcodeHelper.is_jump(code.info.opcode) and code.target is None:
2✔
2182
                    target = Integer.from_bytes(code.raw_data) + VMCodeMapping.instance().get_start_address(code)
2✔
2183
                    if target >= current_bytecode_size:
2✔
2184
                        return True
2✔
2185
        return False
2✔
2186

2187
    def _update_codes_with_target(self, vm_code: VMCode):
2✔
2188
        """
2189
        Verifies if there are any instructions targeting the code. If it exists, updates each instruction found
2190

2191
        :param vm_code: targeted instruction
2192
        """
2193
        instance = VMCodeMapping.instance()
2✔
2194
        vm_code_start_address = instance.get_start_address(vm_code)
2✔
2195
        for target_address, codes in list(self._missing_target.items()):
2✔
2196
            if target_address is not None and target_address <= vm_code_start_address:
2✔
2197
                for code in codes:
2✔
2198
                    code.set_target(vm_code)
2✔
2199
                self._missing_target.pop(target_address)
2✔
2200

2201
    def _update_codes_without_target_to_next(self, address: int = None):
2✔
2202
        if address is None:
2✔
2203
            address = self.bytecode_size
×
2204

2205
        instance = VMCodeMapping.instance()
2✔
2206
        vm_code = instance.get_code(address)
2✔
2207
        if vm_code is None:
2✔
2208
            return
×
2209

2210
        next_address = instance.get_end_address(vm_code)
2✔
2211
        if address in self._missing_target:
2✔
2212
            targets = self._missing_target.pop(address)
×
2213

2214
            if next_address in self._missing_target:
×
2215
                self._missing_target[next_address].extend(targets)
×
2216
            else:
2217
                self._missing_target[next_address] = targets
×
2218

2219
        if None in self._missing_target:
2✔
2220
            for code in self._missing_target[None]:
2✔
2221
                code_address = instance.get_start_address(code)
2✔
2222
                target_address = Integer.from_bytes(code.raw_data) + code_address
2✔
2223
                if target_address == address and target_address != code_address:
2✔
2224
                    data = self._get_jump_data(code.info, next_address - code_address)
2✔
2225
                    instance.update_vm_code(code, code.info, data)
2✔
2226

2227
    def set_code_targets(self):
2✔
2228
        for target, vmcodes in self._missing_target.copy().items():
2✔
2229
            if target is None:
2✔
2230
                for code in vmcodes.copy():
2✔
2231
                    relative_address: int = Integer.from_bytes(code.raw_data, signed=True)
2✔
2232
                    code_address: int = VMCodeMapping.instance().get_start_address(code)
2✔
2233
                    absolute_address = code_address + relative_address
2✔
2234
                    code.set_target(VMCodeMapping.instance().get_code(absolute_address))
2✔
2235

2236
                    vmcodes.remove(code)
2✔
2237
            else:
2238
                for code in vmcodes.copy():
×
2239
                    code.set_target(VMCodeMapping.instance().get_code(target))
×
2240
                    vmcodes.remove(code)
×
2241

2242
            if len(vmcodes) == 0:
2✔
2243
                self._missing_target.pop(target)
2✔
2244

2245
    def _insert_jump(self, op_info: OpcodeInformation, jump_to: int | VMCode = 0, insert_jump: bool = False):
2✔
2246
        """
2247
        Inserts a jump opcode into the bytecode
2248

2249
        :param op_info: info of the opcode  that will be inserted
2250
        :param jump_to: data of the opcode
2251
        :param insert_jump: whether it should be included a jump to the end before the else branch
2252
        """
2253
        if isinstance(jump_to, VMCode):
2✔
2254
            jump_to = VMCodeMapping.instance().get_start_address(jump_to) - VMCodeMapping.instance().bytecode_size
2✔
2255

2256
        if self.last_code.opcode is not Opcode.RET or insert_jump:
2✔
2257
            data: bytes = self._get_jump_data(op_info, jump_to)
2✔
2258
            self.__insert1(op_info, data)
2✔
2259
        for x in range(op_info.stack_items):
2✔
2260
            self._stack_pop()
2✔
2261

2262
    def _update_jump(self, jump_address: int, updated_jump_to: int):
2✔
2263
        """
2264
        Updates the data of a jump code in the bytecode
2265

2266
        :param jump_address: jump code start address
2267
        :param updated_jump_to: new data of the code
2268
        """
2269
        vmcode: VMCode = VMCodeMapping.instance().get_code(jump_address)
2✔
2270
        if vmcode is not None:
2✔
2271
            if updated_jump_to in VMCodeMapping.instance().code_map:
2✔
2272
                self._remove_missing_target(vmcode)
2✔
2273
                target: VMCode = VMCodeMapping.instance().get_code(updated_jump_to)
2✔
2274
                vmcode.set_target(target)
2✔
2275
            else:
2276
                data: bytes = self._get_jump_data(vmcode.info, updated_jump_to - jump_address)
2✔
2277
                VMCodeMapping.instance().update_vm_code(vmcode, vmcode.info, data)
2✔
2278
                if updated_jump_to not in VMCodeMapping.instance().code_map:
2✔
2279
                    self._include_missing_target(vmcode, updated_jump_to)
2✔
2280

2281
    def change_jump(self, jump_address: int, new_jump_opcode: Opcode):
2✔
2282
        """
2283
        Changes the type of jump code in the bytecode
2284

2285
        """
2286
        if not OpcodeHelper.is_jump(new_jump_opcode):
2✔
2287
            return
×
2288

2289
        vmcode: VMCode = VMCodeMapping.instance().get_code(jump_address)
2✔
2290
        if vmcode is not None and OpcodeHelper.is_jump(vmcode.opcode):
2✔
2291
            previous_consumed_items_from_stack = vmcode.info.stack_items
2✔
2292
            new_consumed_items_from_stack = OpcodeInfo.get_info(new_jump_opcode).stack_items
2✔
2293
            if previous_consumed_items_from_stack < new_consumed_items_from_stack:
2✔
2294
                # if previous jump doesn't have condition and the new one has
2295
                previous_stack = self._stack_states.get_state(jump_address)
2✔
2296
                items_to_consume = new_consumed_items_from_stack - previous_consumed_items_from_stack
2✔
2297

2298
                if jump_address == self.last_code_start_address:
2✔
2299
                    for _ in range(items_to_consume):
2✔
2300
                        self._stack_pop()
2✔
2301
                    target_stack_size = len(self._stack)
2✔
2302
                else:
2303
                    target_stack_size = len(previous_stack) - items_to_consume
×
2304

2305
                while len(previous_stack) > target_stack_size:
2✔
2306
                    previous_stack.pop(-1)  # consume last stack item as jump condition
2✔
2307

2308
            vmcode.set_opcode(OpcodeInfo.get_info(new_jump_opcode))
2✔
2309

2310
    def _get_jump_data(self, op_info: OpcodeInformation, jump_to: int) -> bytes:
2✔
2311
        return Integer(jump_to).to_byte_array(min_length=op_info.data_len, signed=True)
2✔
2312

2313
    def _add_to_metadata_permissions(self, contract_class: ContractInterfaceClass, method_name: str):
2✔
2314
        from boa3.internal.compiler.compiledmetadata import CompiledMetadata
2✔
2315
        CompiledMetadata.instance().add_contract_permission(contract_class.contract_hash, method_name)
2✔
2316

2317
    def duplicate_stack_top_item(self):
2✔
2318
        self.duplicate_stack_item(1)
2✔
2319

2320
    def duplicate_stack_item(self, pos: int = 0, *, expected_stack_item: IType = None):
2✔
2321
        """
2322
        Duplicates the item n back in the stack
2323

2324
        :param pos: index of the variable
2325
        """
2326
        # n = 1 -> duplicates stack top item
2327
        # n = 0 -> value varies in runtime
2328
        if pos >= 0:
2✔
2329
            opcode: Opcode = OpcodeHelper.get_dup(pos)
2✔
2330
            if opcode is Opcode.PICK:
2✔
2331
                if pos > 0:
2✔
2332
                    self.convert_literal(pos - 1)
2✔
2333
                    self._stack_pop()
2✔
2334
                elif Type.int.is_type_of(self._stack[-1]) and expected_stack_item is not None:
2✔
2335
                    self._stack_pop()
2✔
2336

2337
            op_info = OpcodeInfo.get_info(opcode)
2✔
2338
            self.__insert1(op_info)
2✔
2339
            if expected_stack_item is None:
2✔
2340
                stacked_value = self._stack[-pos]
2✔
2341
            else:
2342
                stacked_value = expected_stack_item
2✔
2343

2344
            self._stack_append(stacked_value)
2✔
2345

2346
    def move_stack_item_to_top(self, pos: int = 0):
2✔
2347
        """
2348
        Moves the item n back in the stack to the top
2349

2350
        :param pos: index of the variable
2351
        """
2352
        # n = 1 -> stack top item
2353
        # n = 0 -> value varies in runtime
2354
        # do nothing in those cases
2355
        if pos == 2:
2✔
2356
            self.swap_reverse_stack_items(2)
×
2357
        elif pos > 2:
2✔
2358
            self.convert_literal(pos - 1)
2✔
2359
            self._stack_pop()
2✔
2360
            self.__insert1(OpcodeInfo.ROLL)
2✔
2361
            stack_type = self._stack_pop(-pos)
2✔
2362
            self._stack_append(stack_type)
2✔
2363

2364
    def clear_stack(self, clear_if_in_loop: bool = False):
2✔
2365
        if not clear_if_in_loop or len(self._current_for) > 0:
2✔
2366
            for _ in range(self.stack_size):
2✔
2367
                self.__insert1(OpcodeInfo.DROP)
2✔
2368

2369
    def remove_stack_top_item(self):
2✔
2370
        self.remove_stack_item(1)
2✔
2371

2372
    def remove_stack_item(self, pos: int = 0):
2✔
2373
        """
2374
        Removes the item n from the stack
2375

2376
        :param pos: index of the variable
2377
        """
2378
        # n = 1 -> removes stack top item
2379
        if pos > 0:
2✔
2380
            opcode: Opcode = OpcodeHelper.get_drop(pos)
2✔
2381
            if opcode is Opcode.XDROP:
2✔
2382
                self.convert_literal(pos - 1)
2✔
2383
                self._stack_pop()
2✔
2384
            op_info = OpcodeInfo.get_info(opcode)
2✔
2385
            self.__insert1(op_info)
2✔
2386
            if pos > 0 and len(self._stack) > 0:
2✔
2387
                self._stack_pop(-pos)
2✔
2388

2389
    def _remove_inserted_opcodes_since(self, last_address: int, last_stack_size: int | None = None):
2✔
2390
        self._stack_states.restore_state(last_address)
2✔
2391
        if VMCodeMapping.instance().bytecode_size > last_address:
2✔
2392
            # remove opcodes inserted during the evaluation of the symbol
2393
            VMCodeMapping.instance().remove_opcodes(last_address, VMCodeMapping.instance().bytecode_size)
2✔
2394

2395
        if isinstance(last_stack_size, int) and last_stack_size < self.stack_size:
2✔
2396
            # remove any additional values pushed to the stack during the evalution of the symbol
2397
            for _ in range(self.stack_size - last_stack_size):
2✔
2398
                self._stack_pop()
2✔
2399

2400
    def swap_reverse_stack_items(self, no_items: int = 0, rotate: bool = False):
2✔
2401
        # n = 0 -> value varies in runtime
2402
        if 0 <= no_items != 1:
2✔
2403
            opcode: Opcode = OpcodeHelper.get_reverse(no_items, rotate)
2✔
2404
            if opcode is Opcode.REVERSEN and no_items > 0:
2✔
2405
                self.convert_literal(no_items)
2✔
2406
            op_info = OpcodeInfo.get_info(opcode)
2✔
2407
            self.__insert1(op_info)
2✔
2408
            if opcode is Opcode.REVERSEN and no_items > 0:
2✔
2409
                self._stack_pop()
2✔
2410
            if no_items > 0:
2✔
2411
                self._stack.reverse(-no_items, rotate=rotate)
2✔
2412

2413
    def convert_init_user_class(self, class_type: ClassType):
2✔
2414
        if isinstance(class_type, UserClass):
2✔
2415
            # create an none-filled array with the size of the instance variables
2416
            no_instance_variables = len(class_type.instance_variables)
2✔
2417
            self.convert_new_empty_array(no_instance_variables, class_type)
2✔
2418

2419
            self.__insert1(OpcodeInfo.UNPACK)  # unpack for array concatenation
2✔
2420
            value = self._stack_pop()
2✔
2421
            self._stack_append(Type.int)
2✔
2422
            self.remove_stack_top_item()
2✔
2423

2424
            # copy the array that stores the class variables from that class
2425
            self.convert_user_class(class_type, class_type.identifier)
2✔
2426

2427
            self.__insert1(OpcodeInfo.UNPACK)  # unpack for array concatenation
2✔
2428
            self._stack_append(value)
2✔
2429
            self.convert_literal(no_instance_variables)
2✔
2430
            self.convert_operation(BinaryOp.Add)
2✔
2431

2432
            self.__insert1(OpcodeInfo.PACK)  # packs everything together
2✔
2433
            self._stack_pop()
2✔
2434

2435
    def generate_implicit_init_user_class(self, init_method: Method):
2✔
2436
        if not init_method.is_called:
2✔
2437
            return
2✔
2438

2439
        self.convert_begin_method(init_method)
2✔
2440
        class_type = init_method.return_type
2✔
2441
        for base in class_type.bases:
2✔
2442
            base_constructor = base.constructor_method()
2✔
2443
            num_args = len(base_constructor.args)
2✔
2444

2445
            for arg_id, arg_var in reversed(list(init_method.args.items())):
2✔
2446
                self.convert_load_variable(arg_id, arg_var)
2✔
2447

2448
            args_to_call = num_args - 1 if num_args > 0 else num_args
2✔
2449
            self.convert_method_call(base_constructor, args_to_call)
2✔
2450
            self.remove_stack_top_item()
2✔
2451

2452
        self.convert_end_method()
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc