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

CityOfZion / neo3-boa / 547e7781-13d2-42dd-adb2-00b670b3772c

26 Nov 2025 07:56PM UTC coverage: 91.556% (+0.009%) from 91.547%
547e7781-13d2-42dd-adb2-00b670b3772c

Pull #1315

circleci

luc10921
Update ClassPropWithIntEnum test
Pull Request #1315: Reduce amount of opcodes on to_bytes and to_int based on arguments given and fix other misc errors

149 of 152 new or added lines in 7 files covered. (98.03%)

1 existing line in 1 file now uncovered.

22531 of 24609 relevant lines covered (91.56%)

1.83 hits per line

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

95.81
/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(self.last_code, value_type)
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
    def _stack_reverse(self, start: int = 0, end: int = None, *, rotate: bool = False) -> IType:
2✔
278
        return self._stack_states.reverse(self.last_code, start, end, rotate=rotate)
2✔
279

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

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

293
    @property
2✔
294
    def bytecode_size(self) -> int:
2✔
295
        """
296
        Gets the current bytecode size
297

298
        :return: the current bytecode size
299
        """
300
        return VMCodeMapping.instance().bytecode_size
2✔
301

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

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

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

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

320
    @property
2✔
321
    def _globals(self) -> list[str]:
2✔
322
        return self._module_variables(True)
2✔
323

324
    @property
2✔
325
    def _statics(self) -> list[str]:
2✔
326
        return self._module_variables(False)
2✔
327

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

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

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

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

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

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

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

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

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

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

424
                            vars_map[index] = new_value
2✔
425

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

434
                result = reordered_ids + additional_items
2✔
435

436
        return result
2✔
437

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

442
    # region Optimization properties
443

444
    def store_constant_variable(self, var: Variable) -> bool:
2✔
445
        return optimizerhelper.is_storing_static_variable(self._optimization_level, var)
2✔
446

447
    # endregion
448

449
    def is_none_inserted(self) -> bool:
2✔
450
        """
451
        Checks whether the last insertion is null
452

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

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

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

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

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

492
                    if symbol is not None:
2✔
493
                        found_id, found_symbol = identifier, symbol
2✔
494

495
                    elif not isinstance(identifier, str):
2✔
496
                        found_id, found_symbol = identifier, symbol
×
497

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

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

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

522
            return found_id, found_symbol
2✔
523
        return identifier, Type.none
2✔
524

525
    def initialize_static_fields(self) -> tuple[bool, bool]:
2✔
526
        """
527
        Converts the signature of the method
528

529
        :return: whether there are static fields to be initialized and if they can be generated already
530
        """
531
        can_init_static_fields = False
2✔
532
        has_static_fields = False
2✔
533
        default_result = (has_static_fields, can_init_static_fields)
2✔
534

535
        if not self.can_init_static_fields:
2✔
536
            return default_result
2✔
537
        if self.initialized_static_fields:
2✔
538
            return default_result
×
539

540
        num_static_fields = len(self._statics)
2✔
541
        has_static_fields = num_static_fields > 0
2✔
542
        can_init_static_fields = True
2✔
543
        if has_static_fields:
2✔
544
            init_data = bytearray([num_static_fields])
2✔
545
            self.__insert1(OpcodeInfo.INITSSLOT, init_data)
2✔
546

547
            if constants.INITIALIZE_METHOD_ID in self.symbol_table:
2✔
548
                from boa3.internal.helpers import get_auxiliary_name
×
549
                method = self.symbol_table.pop(constants.INITIALIZE_METHOD_ID)
×
550
                new_id = get_auxiliary_name(constants.INITIALIZE_METHOD_ID, method)
×
551
                self.symbol_table[new_id] = method
×
552

553
            init_method = Method(is_public=True)
2✔
554
            init_method.init_bytecode = self.last_code
2✔
555
            self.symbol_table[constants.INITIALIZE_METHOD_ID] = init_method
2✔
556

557
        return has_static_fields, can_init_static_fields
2✔
558

559
    def end_initialize(self):
2✔
560
        """
561
        Converts the signature of the method
562
        """
563
        self.insert_return()
2✔
564
        self.initialized_static_fields = True
2✔
565

566
        if constants.INITIALIZE_METHOD_ID in self.symbol_table:
2✔
567
            init_method = self.symbol_table[constants.INITIALIZE_METHOD_ID]
2✔
568
            init_method.end_bytecode = self.last_code
2✔
569

570
    def convert_begin_method(self, method: Method):
2✔
571
        """
572
        Converts the signature of the method
573

574
        :param method: method that is being converted
575
        """
576
        new_variable_scope = self._scope_stack[-1].copy() if len(self._scope_stack) > 0 else SymbolScope()
2✔
577
        self._scope_stack.append(new_variable_scope)
2✔
578

579
        num_args: int = len(method.args)
2✔
580
        num_vars: int = len(method.locals)
2✔
581

582
        method.init_address = VMCodeMapping.instance().bytecode_size
2✔
583
        if num_args > 0 or num_vars > 0:
2✔
584
            init_data = bytearray([num_vars, num_args])
2✔
585
            self.__insert1(OpcodeInfo.INITSLOT, init_data)
2✔
586
            method.init_bytecode = self.last_code
2✔
587
        self._current_method = method
2✔
588

589
    def convert_end_method(self, method_id: str | None = None):
2✔
590
        """
591
        Converts the end of the method
592
        """
593
        if (self._current_method.init_bytecode is None
2✔
594
                and self._current_method.init_address in VMCodeMapping.instance().code_map):
595
            self._current_method.init_bytecode = VMCodeMapping.instance().code_map[self._current_method.init_address]
2✔
596

597
        if self.last_code.opcode is not Opcode.RET or self._check_codes_with_target():
2✔
598
            if self._current_method.is_init:
2✔
599
                # return the built object if it's a constructor
600
                self_id, self_value = list(self._current_method.args.items())[0]
2✔
601
                self.convert_load_variable(self_id, self_value)
2✔
602

603
            self.insert_return()
2✔
604

605
        self._current_method.end_bytecode = self.last_code
2✔
606
        self._current_method = None
2✔
607
        self._stack.clear()
2✔
608

609
        function_variable_scope = self._scope_stack.pop()
2✔
610

611
    def insert_return(self):
2✔
612
        """
613
        Insert the return statement
614
        """
615
        self.__insert1(OpcodeInfo.RET)
2✔
616

617
    def insert_not(self):
2✔
618
        """
619
        Insert a `not` to change the value of a bool
620
        """
621
        self.__insert1(OpcodeInfo.NOT)
×
622

623
    def insert_nop(self):
2✔
624
        """
625
        Insert a NOP opcode
626
        """
627
        self.__insert1(OpcodeInfo.NOP)
2✔
628

629
    def insert_sys_call(self, sys_call_id: bytes):
2✔
630
        """
631
        Insert a SYSCALL opcode call
632
        """
633
        self.__insert1(OpcodeInfo.SYSCALL, sys_call_id)
2✔
634

635
    def convert_begin_while(self, is_for: bool = False) -> int:
2✔
636
        """
637
        Converts the beginning of the while statement
638

639
        :param is_for: whether the loop is a for loop or not
640
        :return: the address of the while first opcode
641
        """
642
        # it will be updated when the while ends
643
        self._insert_jump(OpcodeInfo.JMP)
2✔
644

645
        start_address = self.last_code_start_address
2✔
646
        self._current_loop.append(start_address)
2✔
647
        if is_for:
2✔
648
            self._current_for.append(start_address)
2✔
649

650
        return start_address
2✔
651

652
    def convert_end_while(self, start_address: int, test_address: int, *, is_internal: bool = False) -> int:
2✔
653
        """
654
        Converts the end of the while statement
655

656
        :param start_address: the address of the while first opcode
657
        :param test_address: the address of the while test fist opcode
658
        :param is_internal: whether it was called when generating other implemented symbols
659
        """
660
        return self.convert_end_loop(start_address, test_address, False, is_internal=is_internal)
2✔
661

662
    def convert_begin_for(self) -> int:
2✔
663
        """
664
        Converts the beginning of the for statement
665

666
        :return: the address of the for first opcode
667
        """
668
        is_neo_iterator = len(self._stack) > 0 and Interop.Iterator.is_type_of(self._stack[-1])
2✔
669
        if is_neo_iterator:
2✔
670
            self.duplicate_stack_top_item()
2✔
671
        else:
672
            self.convert_literal(0)
2✔
673

674
        address = self.convert_begin_while(True)
2✔
675

676
        if is_neo_iterator:
2✔
677
            self.duplicate_stack_top_item()
2✔
678
            self.convert_builtin_method_call(Interop.IteratorValue)
2✔
679
        else:
680
            self.duplicate_stack_item(2)  # duplicate for sequence
2✔
681
            self.duplicate_stack_item(2)  # duplicate for index
2✔
682
            self.convert_get_item()
2✔
683
        return address
2✔
684

685
    def convert_end_for(self, start_address: int, is_internal: bool = False) -> int:
2✔
686
        """
687
        Converts the end of the for statement
688

689
        :param start_address: the address of the for first opcode
690
        :param is_internal: whether it was called when generating other implemented symbols
691
        :return: the address of the loop condition
692
        """
693
        is_neo_iterator = len(self._stack) > 0 and Interop.Iterator.is_type_of(self._stack[-1])
2✔
694
        if not is_neo_iterator:
2✔
695
            self.__insert1(OpcodeInfo.INC)      # index += 1
2✔
696
            if len(self._stack) < 1 or self._stack[-1] is not Type.int:
2✔
697
                self._stack_append(Type.int)
2✔
698

699
        for_increment = self.last_code_start_address
2✔
700
        test_address = VMCodeMapping.instance().bytecode_size
2✔
701
        self._update_continue_jumps(start_address, for_increment)
2✔
702

703
        if is_neo_iterator:
2✔
704
            self.duplicate_stack_top_item()
2✔
705
            self.convert_builtin_method_call(Interop.IteratorNext)
2✔
706
        else:
707
            self.duplicate_stack_top_item()     # dup index and sequence
2✔
708
            self.duplicate_stack_item(3)
2✔
709
            self.convert_builtin_method_call(Builtin.Len)
2✔
710
            self.convert_operation(BinaryOp.Lt)  # continue loop condition: index < len(sequence)
2✔
711

712
        self.convert_end_loop(start_address, test_address, True, is_internal=is_internal)
2✔
713

714
        return test_address
2✔
715

716
    def convert_end_loop(self, start_address: int, test_address: int, is_for: bool, is_internal: bool = False) -> int:
2✔
717
        """
718
        Converts the end of a loop statement
719

720
        :param start_address: the address of the while first opcode
721
        :param test_address: the address of the while test fist opcode
722
        :param is_for: whether the loop is a for loop or not
723
        :param is_internal: whether it was called when generating other implemented symbols
724
        """
725
        # updates the begin jmp with the target address
726
        self._update_jump(start_address, test_address)
2✔
727
        self._update_continue_jumps(start_address, test_address)
2✔
728

729
        # inserts end jmp
730
        while_begin: VMCode = VMCodeMapping.instance().code_map[start_address]
2✔
731
        while_body: int = VMCodeMapping.instance().get_end_address(while_begin) + 1
2✔
732
        end_jmp_to: int = while_body - VMCodeMapping.instance().bytecode_size
2✔
733
        self._insert_jump(OpcodeInfo.JMPIF, end_jmp_to)
2✔
734

735
        self._current_loop.pop()
2✔
736

737
        is_break_pos = self.bytecode_size
2✔
738
        self.convert_literal(False)  # is not break
2✔
739
        is_break_end = self.last_code_start_address
2✔
740
        self._update_break_jumps(start_address)
2✔
741

742
        if is_for:
2✔
743
            self._current_for.pop()
2✔
744
            reverse_to_drop_pos = self.last_code_start_address
2✔
745
            self.swap_reverse_stack_items(3)
2✔
746
            reverse_to_drop_end = self.last_code_start_address
2✔
747

748
            self.remove_stack_top_item()    # removes index and sequence from stack
2✔
749
            self.remove_stack_top_item()
2✔
750

751
            self._insert_loop_break_addresses(start_address, reverse_to_drop_pos, reverse_to_drop_end, self.bytecode_size)
2✔
752

753
        last_opcode = self.bytecode_size
2✔
754
        self._insert_loop_break_addresses(start_address, is_break_pos, is_break_end, last_opcode)
2✔
755
        self._insert_jump(OpcodeInfo.JMPIF)
2✔
756

757
        if is_internal:
2✔
758
            self.convert_end_loop_else(start_address, self.last_code_start_address)
2✔
759
        return last_opcode
2✔
760

761
    def convert_end_loop_else(self, start_address: int, else_begin: int, has_else: bool = False, is_for: bool = False):
2✔
762
        """
763
        Updates the break loops jumps
764

765
        :param start_address: the address of the loop first opcode
766
        :param else_begin: the address of the else first opcode. Equals to code size if has_else is False
767
        :param has_else: whether this loop has an else branch
768
        :param is_for: whether the loop is a for loop or not
769
        """
770
        if start_address in self._jumps_to_loop_break:
2✔
771
            is_loop_insertions = []
2✔
772
            if start_address in self._inserted_loop_breaks:
2✔
773
                is_loop_insertions = self._inserted_loop_breaks.pop(start_address)
2✔
774
            is_loop_insertions.append(else_begin)
2✔
775

776
            if not has_else:
2✔
777
                self._opcodes_to_remove.extend(is_loop_insertions)
2✔
778
            else:
779
                min_break_addresses = 4 if is_for else 3
2✔
780
                if (start_address in self._jumps_to_loop_break
2✔
781
                        and len(self._jumps_to_loop_break[start_address]) < 2
782
                        and len(is_loop_insertions) < min_break_addresses):
783
                    # if len is less than 2, it means it has no breaks or the only break is else branch begin
784
                    # so it can remove the jump in the beginning of else branch
785
                    self._opcodes_to_remove.extend(is_loop_insertions)
2✔
786
                self._update_jump(else_begin, VMCodeMapping.instance().bytecode_size)
2✔
787

788
    def convert_begin_if(self) -> int:
2✔
789
        """
790
        Converts the beginning of the if statement
791

792
        :return: the address of the if first opcode
793
        """
794
        # it will be updated when the if ends
795
        self._insert_jump(OpcodeInfo.JMPIFNOT)
2✔
796
        return VMCodeMapping.instance().get_start_address(self.last_code)
2✔
797

798
    def convert_begin_else(self, start_address: int, insert_jump: bool = False, is_internal: bool = False) -> int:
2✔
799
        """
800
        Converts the beginning of the if else statement
801

802
        :param start_address: the address of the if first opcode
803
        :param insert_jump: whether it should be included a jump to the end before the else branch
804
        :return: the address of the if else first opcode
805
        """
806
        # it will be updated when the if ends
807
        self._insert_jump(OpcodeInfo.JMP, insert_jump=insert_jump)
2✔
808

809
        # updates the begin jmp with the target address
810
        self._update_jump(start_address, VMCodeMapping.instance().bytecode_size)
2✔
811
        if is_internal:
2✔
812
            self._stack_states.restore_state(start_address + 1)
2✔
813

814
        return self.last_code_start_address
2✔
815

816
    def convert_end_if(self, start_address: int, is_internal: bool = False):
2✔
817
        """
818
        Converts the end of the if statement
819

820
        :param start_address: the address of the if first opcode
821
        """
822
        # updates the begin jmp with the target address
823
        self._update_jump(start_address, VMCodeMapping.instance().bytecode_size)
2✔
824
        if is_internal:
2✔
825
            self._stack_states.restore_state(start_address)
2✔
826

827
    def convert_begin_try(self) -> int:
2✔
828
        """
829
        Converts the beginning of the try statement
830

831
        :return: the address of the try first opcode
832
        """
833
        # it will be updated when the while ends
834
        self.__insert_code(TryCode())
2✔
835

836
        return self.last_code_start_address
2✔
837

838
    def convert_try_except(self, exception_id: str | None) -> int:
2✔
839
        """
840
        Converts the end of the try statement
841

842
        :param exception_id: the name identifier of the exception
843
        :type exception_id: str or None
844

845
        :return: the last address from try body
846
        """
847
        self._insert_jump(OpcodeInfo.JMP)
2✔
848
        last_try_code = self.last_code_start_address
2✔
849

850
        self._stack_append(Type.exception)  # when reaching the except body, an exception was raised
2✔
851
        if exception_id is None:
2✔
852
            self.remove_stack_top_item()
2✔
853

854
        return last_try_code
2✔
855

856
    def convert_end_try(self, start_address: int,
2✔
857
                        end_address: int | None = None,
858
                        else_address: int | None = None) -> int:
859
        """
860
        Converts the end of the try statement
861

862
        :param start_address: the address of the try first opcode
863
        :param end_address: the address of the try last opcode. If it is None, there's no except body.
864
        :param else_address: the address of the try else. If it is None, there's no else body.
865
        :return: the last address of the except body
866
        """
867
        self.__insert1(OpcodeInfo.ENDTRY)
2✔
868
        if end_address is not None:
2✔
869
            vmcode_mapping_instance = VMCodeMapping.instance()
2✔
870

871
            try_vm_code = vmcode_mapping_instance.get_code(start_address)
2✔
872
            try_jump = vmcode_mapping_instance.get_code(end_address)
2✔
873

874
            except_start_address = vmcode_mapping_instance.get_end_address(try_jump) + 1
2✔
875
            except_start_code = vmcode_mapping_instance.get_code(except_start_address)
2✔
876

877
            if isinstance(try_vm_code, TryCode):
2✔
878
                try_vm_code.set_except_code(except_start_code)
2✔
879
            self._update_jump(else_address if else_address is not None else end_address, self.last_code_start_address)
2✔
880

881
        return self.last_code_start_address
2✔
882

883
    def convert_end_try_finally(self, last_address: int, start_address: int, has_try_body: bool = False):
2✔
884
        """
885
        Converts the end of the try finally statement
886

887
        :param last_address: the address of the try except last opcode.
888
        :param start_address: the address of the try first opcode
889
        :param has_try_body: whether this try statement has a finally body.
890
        :return: the last address of the except body
891
        """
892
        if has_try_body:
2✔
893
            self.__insert1(OpcodeInfo.ENDFINALLY)
2✔
894
            vmcode_mapping_instance = VMCodeMapping.instance()
2✔
895

896
            try_vm_code = vmcode_mapping_instance.get_code(start_address)
2✔
897
            try_last_code = vmcode_mapping_instance.get_code(last_address)
2✔
898

899
            finally_start_address = vmcode_mapping_instance.get_end_address(try_last_code) + 1
2✔
900
            finally_start_code = vmcode_mapping_instance.get_code(finally_start_address)
2✔
901

902
            if isinstance(try_vm_code, TryCode):
2✔
903
                try_vm_code.set_finally_code(finally_start_code)
2✔
904
            self._update_jump(vmcode_mapping_instance.bytecode_size, self.last_code_start_address)
2✔
905

906
        self._update_jump(last_address, VMCodeMapping.instance().bytecode_size)
2✔
907

908
    def fix_negative_index(self, value_index: int = None, test_is_negative=True):
2✔
909
        self._can_append_target = not self._can_append_target
2✔
910

911
        value_code = self.last_code_start_address
2✔
912
        size = VMCodeMapping.instance().bytecode_size
2✔
913

914
        if test_is_negative:
2✔
915
            self.duplicate_stack_top_item()
2✔
916
            self.__insert1(OpcodeInfo.SIGN)
2✔
917
            self.convert_literal(-1)
2✔
918

919
            jmp_address = VMCodeMapping.instance().bytecode_size
2✔
920
            self._insert_jump(OpcodeInfo.JMPNE)     # if index < 0
2✔
921

922
        state = self._stack_states.get_state(value_index) if isinstance(value_index, int) else self._stack
2✔
923
        # get position of collection relative to top
924
        index_of_last = -1
2✔
925
        for index, value in reversed(list(enumerate(state))):
2✔
926
            if isinstance(value, ICollectionType):
2✔
927
                index_of_last = index
2✔
928
                break
2✔
929

930
        if index_of_last >= 0:
2✔
931
            pos_from_top = len(state) - index_of_last
2✔
932
        else:
933
            pos_from_top = 2
2✔
934

935
        self.duplicate_stack_item(pos_from_top)     # index += len(array)
2✔
936
        self.convert_builtin_method_call(Builtin.Len)
2✔
937
        self.convert_operation(BinaryOp.Add)
2✔
938

939
        if test_is_negative:
2✔
940
            if not isinstance(value_index, int):
2✔
941
                value_index = VMCodeMapping.instance().bytecode_size
2✔
942
            jmp_target = value_index if value_index < size else VMCodeMapping.instance().bytecode_size
2✔
943
            self._update_jump(jmp_address, jmp_target)
2✔
944

945
            VMCodeMapping.instance().move_to_end(value_index, value_code)
2✔
946

947
        self._can_append_target = not self._can_append_target
2✔
948

949
    def fix_index_out_of_range(self, has_another_index_in_stack: bool):
2✔
950
        """
951
        Will fix a negative index to 0 or an index greater than the sequence length to the length.
952

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

955
        :param has_another_index_in_stack: whether the stack is [..., Sequence, index, index] or [..., Sequence, index].
956
        """
957
        # if index is still negative, then it should be 0
958
        self.duplicate_stack_item(2 if has_another_index_in_stack else 1)
2✔
959
        self.__insert1(OpcodeInfo.SIGN)
2✔
960
        self.convert_literal(-1)
2✔
961
        jmp_address = VMCodeMapping.instance().bytecode_size
2✔
962
        self._insert_jump(OpcodeInfo.JMPNE)  # if index < 0, then index = 0
2✔
963

964
        if has_another_index_in_stack:
2✔
965
            self.swap_reverse_stack_items(2)
2✔
966
        self.remove_stack_top_item()
2✔
967
        self.convert_literal(0)
2✔
968
        if has_another_index_in_stack:
2✔
969
            self.swap_reverse_stack_items(2)
2✔
970
        jmp_target = VMCodeMapping.instance().bytecode_size
2✔
971
        self._update_jump(jmp_address, jmp_target)
2✔
972

973
        # index can not be greater than len(string)
974
        self.duplicate_stack_item(3 if has_another_index_in_stack else 2)
2✔
975
        self.convert_builtin_method_call(Builtin.Len)
2✔
976
        self.__insert1(
2✔
977
            OpcodeInfo.MIN)  # the builtin MinMethod accepts more than 2 arguments, that's why this Opcode is being directly inserted
978
        self._stack_pop()
2✔
979

980
    def fix_index_negative_stride(self):
2✔
981
        """
982
        If stride is negative, then the array was reversed, thus, lower and upper should be changed
983
        accordingly.
984
        The Opcodes below will only fix 1 index.
985
        array[lower:upper:-1] == reversed_array[len(array)-lower-1:len(array)-upper-1]
986
        If lower or upper was None, then it's not necessary to change its values.
987
        """
988
        # top array should be: len(array), index
989
        self.convert_builtin_method_call(Builtin.Len)
2✔
990
        self.swap_reverse_stack_items(2)
2✔
991
        self.convert_operation(BinaryOp.Sub)
2✔
992
        self.__insert1(OpcodeInfo.DEC)
2✔
993

994
    def convert_loop_continue(self):
2✔
995
        loop_start = self._current_loop[-1]
2✔
996
        self._insert_jump(OpcodeInfo.JMP)
2✔
997
        continue_address = self.last_code_start_address
2✔
998

999
        if loop_start not in self._jumps_to_loop_condition:
2✔
1000
            self._jumps_to_loop_condition[loop_start] = [continue_address]
2✔
1001
        else:
1002
            self._jumps_to_loop_condition[loop_start].append(continue_address)
×
1003

1004
    def _update_continue_jumps(self, loop_start_address, loop_test_address):
2✔
1005
        if loop_start_address in self._jumps_to_loop_condition:
2✔
1006
            jump_addresses = self._jumps_to_loop_condition.pop(loop_start_address)
2✔
1007
            for address in jump_addresses:
2✔
1008
                self._update_jump(address, loop_test_address)
2✔
1009

1010
    def convert_loop_break(self):
2✔
1011
        loop_start = self._current_loop[-1]
2✔
1012
        is_break_pos = self.bytecode_size
2✔
1013
        self.convert_literal(True)  # is break
2✔
1014
        self._stack_pop()
2✔
1015
        is_break_end = self.last_code_start_address
2✔
1016
        self._insert_jump(OpcodeInfo.JMP)
2✔
1017
        break_address = self.last_code_start_address
2✔
1018

1019
        self._insert_loop_break_addresses(loop_start, is_break_pos, is_break_end, break_address)
2✔
1020

1021
    def _insert_loop_break_addresses(self, loop_start: int, is_break_start: int, is_break_end: int, break_address: int):
2✔
1022
        if loop_start not in self._jumps_to_loop_break:
2✔
1023
            self._jumps_to_loop_break[loop_start] = [break_address]
2✔
1024
        elif break_address not in self._jumps_to_loop_break[loop_start]:
2✔
1025
            self._jumps_to_loop_break[loop_start].append(break_address)
2✔
1026

1027
        is_break_instructions = VMCodeMapping.instance().get_addresses(is_break_start, is_break_end)
2✔
1028

1029
        if loop_start not in self._inserted_loop_breaks:
2✔
1030
            self._inserted_loop_breaks[loop_start] = is_break_instructions
2✔
1031
        else:
1032
            loop_breaks_list = self._inserted_loop_breaks[loop_start]
2✔
1033
            for address in is_break_instructions:
2✔
1034
                # don't include duplicated addresses
1035
                if address not in loop_breaks_list:
2✔
1036
                    loop_breaks_list.append(address)
2✔
1037

1038
    def _update_break_jumps(self, loop_start_address) -> int:
2✔
1039
        jump_target = VMCodeMapping.instance().bytecode_size
2✔
1040

1041
        if loop_start_address in self._jumps_to_loop_break:
2✔
1042
            jump_addresses = self._jumps_to_loop_break.pop(loop_start_address)
2✔
1043
            for address in jump_addresses:
2✔
1044
                self._update_jump(address, jump_target)
2✔
1045

1046
    def convert_literal(self, value: Any) -> int:
2✔
1047
        """
1048
        Converts a literal value
1049

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

1073
    def convert_integer_literal(self, value: int):
2✔
1074
        """
1075
        Converts an integer literal value
1076

1077
        :param value: the value to be converted
1078
        """
1079
        opcode = OpcodeHelper.get_literal_push(value)
2✔
1080
        if opcode is not None:
2✔
1081
            op_info: OpcodeInformation = OpcodeInfo.get_info(opcode)
2✔
1082
            self.__insert1(op_info)
2✔
1083
            self._stack_append(Type.int)
2✔
1084
        else:
1085
            opcode = OpcodeHelper.get_literal_push(-value)
2✔
1086
            if opcode is not None:
2✔
1087
                op_info: OpcodeInformation = OpcodeInfo.get_info(opcode)
2✔
1088
                self.__insert1(op_info)
2✔
1089
                self._stack_append(Type.int)
2✔
1090
                self.convert_operation(UnaryOp.Negative)
2✔
1091
            else:
1092
                opcode, data = OpcodeHelper.get_push_and_data(value)
2✔
1093
                op_info: OpcodeInformation = OpcodeInfo.get_info(opcode)
2✔
1094
                self.__insert1(op_info, data)
2✔
1095
                self._stack_append(Type.int)
2✔
1096

1097
    def convert_string_literal(self, value: str):
2✔
1098
        """
1099
        Converts an string literal value
1100

1101
        :param value: the value to be converted
1102
        """
1103
        array = bytes(value, constants.ENCODING)
2✔
1104
        self.insert_push_data(array)
2✔
1105
        self.convert_cast(Type.str)
2✔
1106

1107
    def convert_bool_literal(self, value: bool):
2✔
1108
        """
1109
        Converts an boolean literal value
1110

1111
        :param value: the value to be converted
1112
        """
1113
        if value:
2✔
1114
            self.__insert1(OpcodeInfo.PUSHT)
2✔
1115
        else:
1116
            self.__insert1(OpcodeInfo.PUSHF)
2✔
1117
        self._stack_append(Type.bool)
2✔
1118

1119
    def convert_sequence_literal(self, sequence: Sequence):
2✔
1120
        """
1121
        Converts a sequence value
1122

1123
        :param sequence: the value to be converted
1124
        """
1125
        if isinstance(sequence, tuple):
2✔
1126
            value_type = Type.tuple.build(sequence)
2✔
1127
        else:
1128
            value_type = Type.list.build(list(sequence))
2✔
1129

1130
        for inner_value in reversed(sequence):
2✔
1131
            self.convert_literal(inner_value)
2✔
1132

1133
        self.convert_new_array(len(sequence), value_type)
2✔
1134

1135
    def convert_dict_literal(self, dictionary: dict):
2✔
1136
        """
1137
        Converts a dict value
1138

1139
        :param dictionary: the value to be converted
1140
        """
1141
        value_type = Type.dict.build(dictionary)
2✔
1142
        self.convert_new_map(value_type)
2✔
1143

1144
        for key, value in dictionary.items():
2✔
1145
            self.duplicate_stack_top_item()
2✔
1146
            self.convert_literal(key)
2✔
1147
            value_start = self.convert_literal(value)
2✔
1148
            self.convert_set_item(value_start)
2✔
1149

1150
    def convert_byte_array(self, array: bytes):
2✔
1151
        """
1152
        Converts a byte value
1153

1154
        :param array: the value to be converted
1155
        """
1156
        self.insert_push_data(array)
2✔
1157
        self.convert_cast(Type.bytearray if isinstance(array, bytearray)
2✔
1158
                          else Type.bytes)
1159

1160
    def insert_push_data(self, data: bytes):
2✔
1161
        """
1162
        Inserts a push data value
1163

1164
        :param data: the value to be converted
1165
        """
1166
        data_len: int = len(data)
2✔
1167
        if data_len <= OpcodeInfo.PUSHDATA1.max_data_len:
2✔
1168
            op_info = OpcodeInfo.PUSHDATA1
2✔
1169
        elif data_len <= OpcodeInfo.PUSHDATA2.max_data_len:
×
1170
            op_info = OpcodeInfo.PUSHDATA2
×
1171
        else:
1172
            op_info = OpcodeInfo.PUSHDATA4
×
1173

1174
        data = Integer(data_len).to_byte_array(min_length=op_info.data_len) + data
2✔
1175
        self.__insert1(op_info, data)
2✔
1176
        self._stack_append(Type.str)  # push data pushes a ByteString value in the stack
2✔
1177

1178
    def insert_none(self):
2✔
1179
        """
1180
        Converts None literal
1181
        """
1182
        self.__insert1(OpcodeInfo.PUSHNULL)
2✔
1183
        self._stack_append(Type.none)
2✔
1184

1185
    def convert_cast(self, value_type: IType, is_internal: bool = False):
2✔
1186
        """
1187
        Converts casting types in Neo VM
1188
        """
1189
        stack_top_type: IType = self._stack[-1]
2✔
1190
        if (not value_type.is_generic
2✔
1191
                and not stack_top_type.is_generic
1192
                and value_type.stack_item is not Type.any.stack_item):
1193

1194
            if is_internal or value_type.stack_item != stack_top_type.stack_item:
2✔
1195
                # converts only if the stack types are different
1196
                self.__insert1(OpcodeInfo.CONVERT, value_type.stack_item)
2✔
1197

1198
            # but changes the value internally
1199
            self._stack_pop()
2✔
1200
            self._stack_append(value_type)
2✔
1201

1202
    def convert_new_map(self, map_type: IType):
2✔
1203
        """
1204
        Converts the creation of a new map
1205

1206
        :param map_type: the Neo Boa type of the map
1207
        """
1208
        self.__insert1(OpcodeInfo.NEWMAP)
2✔
1209
        self._stack_append(map_type)
2✔
1210

1211
    def convert_new_empty_array(self, length: int, array_type: IType, *, as_struct: bool = False):
2✔
1212
        """
1213
        Converts the creation of a new empty array
1214

1215
        :param length: the size of the new array
1216
        :param array_type: the Neo Boa type of the array
1217
        :param as_struct: convert as struct instead of array
1218
        """
1219
        if length <= 0:
2✔
1220
            self.__insert1(OpcodeInfo.NEWARRAY0 if not as_struct else OpcodeInfo.NEWSTRUCT0)
2✔
1221
        else:
1222
            self.convert_literal(length)
2✔
1223
            self._stack_pop()
2✔
1224
            self.__insert1(OpcodeInfo.NEWARRAY if not as_struct else OpcodeInfo.NEWSTRUCT)
2✔
1225
        self._stack_append(array_type)
2✔
1226

1227
    def convert_new_array(self, length: int, array_type: IType = Type.list):
2✔
1228
        """
1229
        Converts the creation of a new array
1230

1231
        :param length: the size of the new array
1232
        :param array_type: the Neo Boa type of the array
1233
        """
1234
        if length <= 0:
2✔
1235
            self.convert_new_empty_array(length, array_type)
2✔
1236
        else:
1237
            self.convert_literal(length)
2✔
1238
            if array_type.stack_item is StackItemType.Struct:
2✔
1239
                self.__insert1(OpcodeInfo.PACKSTRUCT)
×
1240
            else:
1241
                self.__insert1(OpcodeInfo.PACK)
2✔
1242
            self._stack_pop()  # array size
2✔
1243
            for x in range(length):
2✔
1244
                self._stack_pop()
2✔
1245
            self._stack_append(array_type)
2✔
1246

1247
    def _set_array_item(self, value_start_address: int, check_for_negative_index: bool = True):
2✔
1248
        """
1249
        Converts the end of setting af a value in an array
1250
        """
1251
        index_type: IType = self._stack[-2]  # top: index
2✔
1252
        if index_type is Type.int and check_for_negative_index:
2✔
1253
            self.fix_negative_index(value_start_address)
2✔
1254

1255
    def convert_set_item(self, value_start_address: int, index_inserted_internally: bool = False):
2✔
1256
        """
1257
        Converts the end of setting af a value in an array
1258
        """
1259
        item_type: IType = self._stack[-3]  # top: index, 2nd-to-top: value, 3nd-to-top: array or map
2✔
1260
        if item_type.stack_item is not StackItemType.Map:
2✔
1261
            self._set_array_item(value_start_address, check_for_negative_index=not index_inserted_internally)
2✔
1262

1263
        self.__insert1(OpcodeInfo.SETITEM)
2✔
1264
        self._stack_pop()  # value
2✔
1265
        self._stack_pop()  # index
2✔
1266
        self._stack_pop()  # array or map
2✔
1267

1268
    def _get_array_item(self, check_for_negative_index: bool = True, test_is_negative_index=True):
2✔
1269
        """
1270
        Converts the end of get a value in an array
1271
        """
1272
        index_type: IType = self._stack[-1]  # top: index
2✔
1273
        if index_type is Type.int and check_for_negative_index:
2✔
1274
            self.fix_negative_index(test_is_negative=test_is_negative_index)
2✔
1275

1276
    def convert_get_item(self, index_inserted_internally: bool = False, index_is_positive=False, test_is_negative_index=True):
2✔
1277
        array_or_map_type: IType = self._stack[-2]  # second-to-top: array or map
2✔
1278
        if array_or_map_type.stack_item is not StackItemType.Map:
2✔
1279
            self._get_array_item(check_for_negative_index=not (index_inserted_internally or index_is_positive),
2✔
1280
                                 test_is_negative_index=test_is_negative_index)
1281

1282
        if array_or_map_type is Type.str:
2✔
1283
            self.convert_literal(1)  # length of substring
2✔
1284
            self.convert_get_substring(is_internal=index_inserted_internally or index_is_positive or not test_is_negative_index)
2✔
1285
        else:
1286
            self.__insert1(OpcodeInfo.PICKITEM)
2✔
1287
            self._stack_pop()
2✔
1288
            self._stack_pop()
2✔
1289
            if hasattr(array_or_map_type, 'value_type'):
2✔
1290
                new_stack_item = array_or_map_type.value_type
2✔
1291
            else:
1292
                new_stack_item = Type.any
2✔
1293
            self._stack_append(new_stack_item)
2✔
1294

1295
    def convert_get_substring(self, *, is_internal: bool = False, fix_result_type: bool = True):
2✔
1296
        """
1297
        Converts the end of get a substring
1298

1299
        :param is_internal: whether it was called when generating other implemented symbols
1300
        """
1301
        if not is_internal:
2✔
1302
            # if given substring size is negative, return empty string
1303
            self.duplicate_stack_top_item()
2✔
1304
            self.convert_literal(0)
2✔
1305
            self.convert_operation(BinaryOp.GtE)
2✔
1306

1307
            self._insert_jump(OpcodeInfo.JMPIF)
2✔
1308
            jmp_address = self.last_code_start_address
2✔
1309
            self.remove_stack_top_item()
2✔
1310
            self.convert_literal(0)
2✔
1311

1312
        self._stack_pop()  # length
2✔
1313
        self._stack_pop()  # start
2✔
1314
        original = self._stack_pop()  # original string
2✔
1315

1316
        self.__insert1(OpcodeInfo.SUBSTR)
2✔
1317
        if not is_internal:
2✔
1318
            self._update_jump(jmp_address, self.last_code_start_address)
2✔
1319
        self._stack_append(BufferType)  # substr returns a buffer instead of a bytestring
2✔
1320
        if fix_result_type:
2✔
1321
            self.convert_cast(original)
2✔
1322

1323
    def convert_get_array_slice(self, array: SequenceType):
2✔
1324
        """
1325
        Converts the end of get a substring
1326
        """
1327
        self.convert_new_empty_array(0, array)      # slice = []
2✔
1328
        self.duplicate_stack_item(3)                # index = slice_start
2✔
1329

1330
        start_jump = self.convert_begin_while()  # while index < slice_end
2✔
1331
        self.duplicate_stack_top_item()             # if index >= slice_start
2✔
1332
        self.duplicate_stack_item(5)
2✔
1333
        self.convert_operation(BinaryOp.GtE)
2✔
1334
        is_valid_index = self.convert_begin_if()
2✔
1335

1336
        self.duplicate_stack_item(2)                    # slice.append(array[index])
2✔
1337
        self.duplicate_stack_item(6)
2✔
1338
        self.duplicate_stack_item(3)
2✔
1339
        self.convert_get_item()
2✔
1340
        self.convert_builtin_method_call(Builtin.SequenceAppend.build(array))
2✔
1341
        self.convert_end_if(is_valid_index)
2✔
1342

1343
        self.__insert1(OpcodeInfo.INC)              # index += 1
2✔
1344

1345
        condition_address = VMCodeMapping.instance().bytecode_size
2✔
1346
        self.duplicate_stack_top_item()         # end while index < slice_end
2✔
1347
        self.duplicate_stack_item(4)
2✔
1348
        self.convert_operation(BinaryOp.Lt)
2✔
1349
        self.convert_end_while(start_jump, condition_address)
2✔
1350

1351
        self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
2✔
1352
        self.remove_stack_top_item()        # removes from the stack the arguments and the index
2✔
1353
        self.swap_reverse_stack_items(4)    # doesn't use CLEAR opcode because this would delete
2✔
1354
        self.remove_stack_top_item()        # data from external scopes
2✔
1355
        self.remove_stack_top_item()
2✔
1356
        self.remove_stack_top_item()
2✔
1357

1358
    def convert_get_sub_sequence(self):
2✔
1359
        """
1360
        Gets a slice of an array or ByteString
1361
        """
1362
        # top: length, index, array
1363
        if len(self._stack) > 2 and isinstance(self._stack[-3], SequenceType):
2✔
1364

1365
            if self._stack[-3].stack_item in (StackItemType.ByteString,
2✔
1366
                                              StackItemType.Buffer):
1367
                self.duplicate_stack_item(2)
2✔
1368
                self.convert_operation(BinaryOp.Sub)
2✔
1369
                self.convert_get_substring()
2✔
1370
            else:
1371
                array = self._stack[-3]
2✔
1372
                self.convert_get_array_slice(array)
2✔
1373

1374
    def convert_get_sequence_beginning(self):
2✔
1375
        """
1376
        Gets the beginning slice of an array or ByteString
1377
        """
1378
        if len(self._stack) > 1 and isinstance(self._stack[-2], SequenceType):
2✔
1379
            if self._stack[-2].stack_item in (StackItemType.ByteString,
2✔
1380
                                              StackItemType.Buffer):
1381
                self.__insert1(OpcodeInfo.LEFT)
2✔
1382
                self._stack_pop()  # length
2✔
1383
                original_type = self._stack_pop()  # original array
2✔
1384
                self._stack_append(BufferType)  # left returns a buffer instead of a bytestring
2✔
1385
                self.convert_cast(original_type)
2✔
1386
            else:
1387
                array = self._stack[-2]
2✔
1388

1389
                self.convert_literal(0)
2✔
1390
                self.swap_reverse_stack_items(2)
2✔
1391
                self.convert_get_array_slice(array)
2✔
1392

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

1413
                self.duplicate_stack_item(2)
2✔
1414
                self.convert_builtin_method_call(Builtin.Len)
2✔
1415

1416
                self.convert_get_array_slice(array)
2✔
1417

1418
    def convert_copy(self):
2✔
1419
        if self._stack[-1].stack_item is StackItemType.Array:
2✔
1420
            self.__insert1(OpcodeInfo.UNPACK)
2✔
1421
            self.__insert1(OpcodeInfo.PACK)    # creates a new array with the values
2✔
1422

1423
    def convert_get_stride(self):
2✔
1424
        if len(self._stack) > 1 and isinstance(self._stack[-2], SequenceType):
2✔
1425
            if self._stack[-2].stack_item in (StackItemType.ByteString, StackItemType.Buffer):
2✔
1426
                self.convert_get_substring_stride()
2✔
1427
            else:
1428
                self.convert_get_array_stride()
2✔
1429

1430
    def convert_array_negative_stride(self):
2✔
1431
        """
1432
        Converts an array to its reverse, to be able to scroll through the reversed list
1433
        """
1434
        # The logic on this function only do variable[::-z]
1435

1436
        original = self._stack[-1]
2✔
1437
        self.convert_builtin_method_call(Builtin.Reversed)
2✔
1438
        self.convert_cast(ListType())
2✔
1439
        if isinstance(original, (StrType, BytesType)):        # if self was a string/bytes, then concat the values in the array
2✔
1440
            self.duplicate_stack_top_item()
2✔
1441
            self.convert_builtin_method_call(Builtin.Len)       # index = len(array) - 1
2✔
1442
            self.convert_literal('')                            # string = ''
2✔
1443

1444
            start_jump = self.convert_begin_while()
2✔
1445
            self.duplicate_stack_item(3)
2✔
1446
            self.duplicate_stack_item(3)
2✔
1447
            str_type = self._stack[-3]
2✔
1448
            self.__insert1(OpcodeInfo.PICKITEM)
2✔
1449
            self._stack_pop()
2✔
1450
            self._stack_pop()
2✔
1451
            self._stack_append(str_type)
2✔
1452
            self.swap_reverse_stack_items(2)
2✔
1453
            self.convert_operation(BinaryOp.Concat)             # string = string + array[index]
2✔
1454

1455
            condition_address = VMCodeMapping.instance().bytecode_size
2✔
1456
            self.swap_reverse_stack_items(2)
2✔
1457
            self.__insert1(OpcodeInfo.DEC)                      # index--
2✔
1458
            self.swap_reverse_stack_items(2)
2✔
1459
            self.duplicate_stack_item(2)
2✔
1460
            self.convert_literal(0)
2✔
1461
            self.convert_operation(BinaryOp.GtE)                # if index <= 0, stop loop
2✔
1462
            self.convert_end_while(start_jump, condition_address)
2✔
1463
            self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
2✔
1464

1465
            # remove auxiliary values
1466
            self.swap_reverse_stack_items(3)
2✔
1467
            self.remove_stack_top_item()
2✔
1468
            self.remove_stack_top_item()
2✔
1469

1470
    def convert_get_substring_stride(self):
2✔
1471
        # initializing auxiliary variables
1472
        self.duplicate_stack_item(2)
2✔
1473
        self.convert_builtin_method_call(Builtin.Len)
2✔
1474
        self.convert_literal(0)                         # index = 0
2✔
1475
        self.convert_literal('')                        # substr = ''
2✔
1476

1477
        # logic verifying if substr[index] should be concatenated or not
1478
        start_jump = self.convert_begin_while()
2✔
1479
        self.duplicate_stack_item(2)
2✔
1480
        self.duplicate_stack_item(5)
2✔
1481
        self.convert_operation(BinaryOp.Mod)
2✔
1482
        self.convert_literal(0)
2✔
1483
        self.convert_operation(BinaryOp.NumEq)
2✔
1484
        is_mod_0 = self.convert_begin_if()              # if index % stride == 0, then concatenate it
2✔
1485

1486
        # concatenating substr[index] with substr
1487
        self.duplicate_stack_item(5)
2✔
1488
        self.duplicate_stack_item(3)
2✔
1489
        self.convert_literal(1)
2✔
1490
        self._stack_pop()  # length
2✔
1491
        self._stack_pop()  # start
2✔
1492
        str_type = self._stack_pop()
2✔
1493
        self.__insert1(OpcodeInfo.SUBSTR)
2✔
1494
        self._stack_append(BufferType)                  # SUBSTR returns a buffer instead of a bytestring
2✔
1495
        self.convert_cast(str_type)
2✔
1496
        self.convert_operation(BinaryOp.Concat)         # substr = substr + string[index]
2✔
1497
        self.convert_end_if(is_mod_0)
2✔
1498

1499
        # increment the index by 1
1500
        self.swap_reverse_stack_items(2)
2✔
1501
        self.__insert1(OpcodeInfo.INC)                  # index++
2✔
1502
        self.swap_reverse_stack_items(2)
2✔
1503

1504
        # verifying if it should still be in the while
1505
        condition_address = VMCodeMapping.instance().bytecode_size
2✔
1506
        self.duplicate_stack_item(2)
2✔
1507
        self.duplicate_stack_item(4)
2✔
1508
        self.convert_operation(BinaryOp.Lt)             # stop the loop when index >= len(str)
2✔
1509
        self.convert_end_while(start_jump, condition_address)
2✔
1510
        self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
2✔
1511

1512
        # removing auxiliary values
1513
        self.swap_reverse_stack_items(5)
2✔
1514
        self.remove_stack_top_item()
2✔
1515
        self.remove_stack_top_item()
2✔
1516
        self.remove_stack_top_item()
2✔
1517
        self.remove_stack_top_item()
2✔
1518

1519
    def convert_get_array_stride(self):
2✔
1520
        # initializing auxiliary variable
1521
        self.duplicate_stack_item(2)
2✔
1522
        self.convert_builtin_method_call(Builtin.Len)
2✔
1523
        self.__insert1(OpcodeInfo.DEC)                      # index = len(array) - 1
2✔
1524

1525
        # logic verifying if array[index] should be removed or not
1526
        start_jump = self.convert_begin_while()
2✔
1527
        self.duplicate_stack_item(2)
2✔
1528
        self.duplicate_stack_item(2)
2✔
1529
        self.swap_reverse_stack_items(2)
2✔
1530
        self.convert_operation(BinaryOp.Mod)
2✔
1531
        self.convert_literal(0)
2✔
1532
        self.convert_operation(BinaryOp.NumNotEq)
2✔
1533
        is_not_mod_0 = self.convert_begin_if()              # if index % stride != 0, then remove it
2✔
1534

1535
        # removing element from array
1536
        self.duplicate_stack_item(3)
2✔
1537
        self.duplicate_stack_item(2)
2✔
1538
        self.__insert1(OpcodeInfo.REMOVE)                   # array.pop(index)
2✔
1539
        self._stack_pop()
2✔
1540
        self._stack_pop()
2✔
1541
        self.convert_end_if(is_not_mod_0)
2✔
1542

1543
        # decrement 1 from index
1544
        self.__insert1(OpcodeInfo.DEC)
2✔
1545

1546
        # verifying if it should still be in the while
1547
        condition_address = VMCodeMapping.instance().bytecode_size
2✔
1548
        self.duplicate_stack_top_item()
2✔
1549
        self.__insert1(OpcodeInfo.SIGN)
2✔
1550
        self.convert_literal(-1)
2✔
1551
        self.convert_operation(BinaryOp.NumNotEq)       # stop the loop when index < 0
2✔
1552
        self.convert_end_while(start_jump, condition_address)
2✔
1553
        self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
2✔
1554

1555
        # removing auxiliary values
1556
        self.remove_stack_top_item()                    # removed index from stack
2✔
1557
        self.remove_stack_top_item()                    # removed stride from stack
2✔
1558

1559
    def convert_starred_variable(self):
2✔
1560
        top_stack_item = self._stack[-1].stack_item
2✔
1561
        if top_stack_item is StackItemType.Array:
2✔
1562
            self.convert_copy()
2✔
1563
        elif top_stack_item is StackItemType.Map:
×
1564
            self.convert_builtin_method_call(Builtin.DictKeys)
×
1565
        else:
1566
            return
×
1567

1568
        self.convert_cast(Type.tuple)
2✔
1569

1570
    def convert_load_symbol(self, symbol_id: str, params_addresses: list[int] = None, is_internal: bool = False,
2✔
1571
                            class_type: UserClass | None = None):
1572
        """
1573
        Converts the load of a symbol
1574

1575
        :param symbol_id: the symbol identifier
1576
        :param params_addresses: a list with each function arguments' first addresses
1577
        """
1578
        another_symbol_id, symbol = self.get_symbol(symbol_id, is_internal=is_internal)
2✔
1579

1580
        if class_type is not None and symbol_id in class_type.symbols:
2✔
1581
            symbol = class_type.symbols[symbol_id]
2✔
1582

1583
        if symbol is not Type.none:
2✔
1584
            if isinstance(symbol, Property):
2✔
1585
                symbol = symbol.getter
2✔
1586
                params_addresses = []
2✔
1587
            elif isinstance(symbol, ClassType) and params_addresses is not None:
2✔
1588
                symbol = symbol.constructor_method()
2✔
1589

1590
            if not params_addresses:
2✔
1591
                params_addresses = []
2✔
1592

1593
            if isinstance(symbol, Variable):
2✔
1594
                symbol_id = another_symbol_id
2✔
1595
                self.convert_load_variable(symbol_id, symbol, class_type)
2✔
1596
            elif isinstance(symbol, IBuiltinMethod) and symbol.body is None:
2✔
1597
                self.convert_builtin_method_call(symbol, params_addresses)
2✔
1598
            elif isinstance(symbol, Event):
2✔
1599
                self.convert_event_call(symbol)
2✔
1600
            elif isinstance(symbol, Method):
2✔
1601
                self.convert_method_call(symbol, len(params_addresses))
2✔
1602
            elif isinstance(symbol, UserClass):
2✔
1603
                self.convert_class_symbol(symbol, symbol_id)
2✔
1604

1605
    def convert_load_class_variable(self, class_type: ClassType, var_id: str, is_internal: bool = False):
2✔
1606
        variable_list = (class_type._all_variables
2✔
1607
                         if hasattr(class_type, '_all_variables') and is_internal
1608
                         else class_type.variables)
1609

1610
        if var_id in variable_list:
2✔
1611
            var = variable_list[var_id]
2✔
1612
            index = list(variable_list).index(var_id)
2✔
1613
            self.convert_literal(index)
2✔
1614
            self.convert_get_item(index_inserted_internally=True)
2✔
1615
            self._stack_pop()  # pop class type
2✔
1616
            self._stack_append(var.type)  # push variable type
2✔
1617

1618
    def convert_load_variable(self, var_id: str, var: Variable, class_type: UserClass | None = None):
2✔
1619
        """
1620
        Converts the assignment of a variable
1621

1622
        :param var_id: the value to be converted
1623
        :param var: the actual variable to be loaded
1624
        """
1625
        index, local, is_arg = self._get_variable_info(var_id)
2✔
1626
        if index >= 0:
2✔
1627
            opcode = OpcodeHelper.get_load(index, local, is_arg)
2✔
1628
            op_info = OpcodeInfo.get_info(opcode)
2✔
1629

1630
            if op_info.data_len > 0:
2✔
1631
                self.__insert1(op_info, Integer(index).to_byte_array())
2✔
1632
            else:
1633
                self.__insert1(op_info)
2✔
1634
            self._stack_append(var.type)
2✔
1635

1636
        elif hasattr(var.type, 'get_value'):
2✔
1637
            # the variable is a type constant
1638
            value = var.type.get_value(var_id.split(constants.ATTRIBUTE_NAME_SEPARATOR)[-1])
2✔
1639
            if value is not None:
2✔
1640
                self.convert_literal(value)
2✔
1641

1642
        elif var_id in self._globals:
2✔
1643
            another_var_id, var = self.get_symbol(var_id)
2✔
1644
            storage_key = codegenerator.get_storage_key_for_variable(var)
2✔
1645
            self._convert_builtin_storage_get_or_put(True, storage_key)
2✔
1646

1647
        elif var.is_global:
2✔
1648
            if not self.store_constant_variable(var):
2✔
1649
                self.convert_literal(var._first_assign_value)
2✔
1650

1651
        elif class_type:
2✔
1652
            self.convert_load_class_variable(class_type, var_id)
2✔
1653

1654
    def convert_store_variable(self, var_id: str, value_start_address: int = None, user_class: UserClass = None):
2✔
1655
        """
1656
        Converts the assignment of a variable
1657

1658
        :param var_id: the value to be converted
1659
        """
1660
        inner_index = None
2✔
1661
        index, local, is_arg = self._get_variable_info(var_id)
2✔
1662

1663
        if user_class is None and len(self._stack) > 1 and isinstance(self._stack[-2], UserClass):
2✔
1664
            user_class = self._stack[-2]
2✔
1665

1666
        if isinstance(user_class, UserClass) and var_id in user_class.variables:
2✔
1667
            index, local, is_arg = self._get_variable_info(user_class.identifier)
2✔
1668
            inner_index = list(user_class.variables).index(var_id)
2✔
1669

1670
        if isinstance(inner_index, int):
2✔
1671
            # it's a variable from a class
1672
            self.convert_literal(inner_index)
2✔
1673
            index_address = self.bytecode_size
2✔
1674

1675
            if var_id in user_class.class_variables:
2✔
1676
                # it's a class variable
1677
                self.convert_load_variable(user_class.identifier, Variable(user_class))
2✔
1678
                no_stack_items_to_swap = 3
2✔
1679
            else:
1680
                no_stack_items_to_swap = 2
2✔
1681

1682
            self.swap_reverse_stack_items(no_stack_items_to_swap)
2✔
1683
            self.convert_set_item(index_address, index_inserted_internally=True)
2✔
1684
            return
2✔
1685

1686
        if index >= 0:
2✔
1687
            opcode = OpcodeHelper.get_store(index, local, is_arg)
2✔
1688
            if opcode is not None:
2✔
1689
                op_info = OpcodeInfo.get_info(opcode)
2✔
1690

1691
                if op_info.data_len > 0:
2✔
1692
                    self.__insert1(op_info, Integer(index).to_byte_array())
2✔
1693
                else:
1694
                    self.__insert1(op_info)
2✔
1695
                stored_type = self._stack_pop()
2✔
1696

1697
                from boa3.internal.analyser.model.optimizer import UndefinedType
2✔
1698
                if (var_id in self._current_scope.symbols or
2✔
1699
                        (var_id in self._locals and self._current_method.locals[var_id].type is UndefinedType)):
1700
                    another_symbol_id, symbol = self.get_symbol(var_id)
2✔
1701
                    if isinstance(symbol, Variable):
2✔
1702
                        var = symbol.copy()
2✔
1703
                        var.set_type(stored_type)
2✔
1704
                        self._current_scope.include_symbol(var_id, var)
2✔
1705

1706
        elif var_id in self._globals:
2✔
1707
            another_var_id, var = self.get_symbol(var_id)
2✔
1708
            storage_key = codegenerator.get_storage_key_for_variable(var)
2✔
1709
            if value_start_address is None:
2✔
1710
                value_start_address = self.bytecode_size
×
1711
            self._convert_builtin_storage_get_or_put(False, storage_key, value_start_address)
2✔
1712

1713
    def _convert_builtin_storage_get_or_put(self, is_get: bool, storage_key: bytes, arg_address: int = None):
2✔
1714
        addresses = [arg_address] if arg_address is not None else [self.bytecode_size]
2✔
1715
        if not is_get:
2✔
1716
            # must serialized before storing the value
1717
            self.convert_builtin_method_call(Interop.Serialize, addresses)
2✔
1718

1719
        self.convert_literal(storage_key)
2✔
1720
        self.convert_builtin_method_call(Interop.StorageGetContext)
2✔
1721

1722
        builtin_method = Interop.StorageGet if is_get else Interop.StoragePut
2✔
1723
        self.convert_builtin_method_call(builtin_method)
2✔
1724

1725
        if is_get:
2✔
1726
            # once the value is retrieved, it must be deserialized
1727
            self.convert_builtin_method_call(Interop.Deserialize, addresses)
2✔
1728

1729
    def _get_variable_info(self, var_id: str) -> tuple[int, bool, bool]:
2✔
1730
        """
1731
        Gets the necessary information about the variable to get the correct opcode
1732

1733
        :param var_id: the name id of the
1734
        :return: returns the index of the variable in its scope and two boolean variables for representing the
1735
        variable scope:
1736
            `local` is True if it is a local variable and
1737
            `is_arg` is True only if the variable is a parameter of the function.
1738
        If the variable is not found, returns (-1, False, False)
1739
        """
1740
        is_arg: bool = False
2✔
1741
        local: bool = False
2✔
1742
        scope = None
2✔
1743

1744
        if var_id in self._args:
2✔
1745
            is_arg: bool = True
2✔
1746
            local: bool = True
2✔
1747
            scope = self._args
2✔
1748
        elif var_id in self._locals:
2✔
1749
            is_arg = False
2✔
1750
            local: bool = True
2✔
1751
            scope = self._locals
2✔
1752
        elif var_id in self._statics:
2✔
1753
            scope = self._statics
2✔
1754

1755
        if scope is not None:
2✔
1756
            index: int = scope.index(var_id) if var_id in scope else -1
2✔
1757
        else:
1758
            index = -1
2✔
1759

1760
        return index, local, is_arg
2✔
1761

1762
    def convert_builtin_method_call(self, function: IBuiltinMethod, args_address: list[int] = None, is_internal: bool = False):
2✔
1763
        """
1764
        Converts a builtin method function call
1765

1766
        :param function: the function to be converted
1767
        :param args_address: a list with each function arguments' first addresses
1768
        :param is_internal: whether it was called when generating other implemented symbols
1769
        """
1770
        stack_before = len(self._stack)
2✔
1771
        if args_address is None:
2✔
1772
            args_address = []
2✔
1773
        store_opcode: OpcodeInformation = None
2✔
1774
        store_data: bytes = b''
2✔
1775

1776
        if function.pack_arguments:
2✔
1777
            self.convert_new_array(len(args_address))
×
1778

1779
        if function.stores_on_slot and 0 < len(function.args) <= len(args_address):
2✔
1780
            address = args_address[-len(function.args)]
2✔
1781
            load_instr = VMCodeMapping.instance().code_map[address]
2✔
1782
            if OpcodeHelper.is_load_slot(load_instr.opcode):
2✔
1783
                store: Opcode = OpcodeHelper.get_store_from_load(load_instr.opcode)
2✔
1784
                store_opcode = OpcodeInfo.get_info(store)
2✔
1785
                store_data = load_instr.data
2✔
1786

1787
        fix_negatives = function.validate_negative_arguments()
2✔
1788
        if len(fix_negatives) > 0:
2✔
1789
            args_end_addresses = args_address[1:]
2✔
1790
            args_end_addresses.append(self.bytecode_size)
2✔
1791

1792
            if function.push_self_first():
2✔
1793
                addresses = args_end_addresses[:1] + list(reversed(args_end_addresses[1:]))
2✔
1794
            else:
1795
                addresses = list(reversed(args_end_addresses))
×
1796

1797
            for arg in sorted(fix_negatives, reverse=True):
2✔
1798
                if len(addresses) > arg:
2✔
1799
                    self.fix_negative_index(addresses[arg])
2✔
1800

1801
        self._convert_builtin_call(function, previous_stack_size=stack_before, is_internal=is_internal)
2✔
1802

1803
        if store_opcode is not None:
2✔
1804
            self._insert_jump(OpcodeInfo.JMP)
2✔
1805
            self._update_codes_without_target_to_next(self.last_code_start_address)
2✔
1806
            jump = self.last_code_start_address
2✔
1807
            self.__insert1(store_opcode, store_data)
2✔
1808
            self._update_jump(jump, VMCodeMapping.instance().bytecode_size)
2✔
1809

1810
    def _convert_builtin_call(self, builtin: IBuiltinCallable, previous_stack_size: int = None, is_internal: bool = False):
2✔
1811
        if not isinstance(previous_stack_size, int):
2✔
1812
            previous_stack_size = len(self._stack)
2✔
1813
        elif previous_stack_size < 0:
2✔
1814
            previous_stack_size = 0
×
1815

1816
        size_before_generating = self.bytecode_size
2✔
1817
        if is_internal:
2✔
1818
            builtin.generate_internal_opcodes(self)
2✔
1819
        else:
1820
            builtin.generate_opcodes(self)
2✔
1821

1822
        if isinstance(builtin, IBuiltinMethod):
2✔
1823
            if is_internal and hasattr(builtin, 'internal_call_args'):
2✔
1824
                expected_stack_after = previous_stack_size - builtin.internal_call_args
2✔
1825
            else:
1826
                expected_stack_after = previous_stack_size - builtin.args_on_stack
2✔
1827
        else:
1828
            expected_stack_after = previous_stack_size - len(builtin.args)
2✔
1829

1830
        if expected_stack_after < 0:
2✔
1831
            expected_stack_after = 0
2✔
1832

1833
        while expected_stack_after < len(self._stack):
2✔
1834
            self._stack_pop()
2✔
1835
        if builtin.return_type not in (None, Type.none):
2✔
1836
            self._stack_append(builtin.return_type)
2✔
1837

1838
    def convert_method_call(self, function: Method, num_args: int):
2✔
1839
        """
1840
        Converts a method function call
1841

1842
        :param function: the function to be converted
1843
        """
1844
        if function.is_init:
2✔
1845
            if num_args == len(function.args):
2✔
1846
                self.remove_stack_top_item()
×
1847
                num_args -= 1
×
1848

1849
            if num_args == len(function.args) - 1:
2✔
1850
                # if this method is a constructor and only the self argument is missing
1851
                function_result = function.type
2✔
1852
                size = len(function_result.variables) if isinstance(function_result, UserClass) else 0
2✔
1853
                if self.stack_size < 1 or not self._stack[-1].is_type_of(function_result):
2✔
1854
                    self.convert_new_empty_array(size, function_result)
×
1855

1856
        if isinstance(function.origin_class, ContractInterfaceClass):
2✔
1857
            if function.external_name is not None:
2✔
1858
                function_id = function.external_name
2✔
1859
            else:
1860
                function_id = next((symbol_id
2✔
1861
                                    for symbol_id, symbol in function.origin_class.symbols.items()
1862
                                    if symbol is function),
1863
                                   None)
1864
            self._add_to_metadata_permissions(function.origin_class, function_id)
2✔
1865

1866
            if isinstance(function_id, str):
2✔
1867
                self.convert_new_array(len(function.args))
2✔
1868
                self.convert_literal(Interop.CallFlagsType.default_value)
2✔
1869
                self.convert_literal(function_id)
2✔
1870
                self.convert_literal(function.origin_class.contract_hash.to_array())
2✔
1871
                self.convert_builtin_method_call(Interop.CallContract)
2✔
1872
                self._stack_pop()  # remove call contract 'any' result from the stack
2✔
1873
                self._stack_append(function.return_type)    # add the return type on the stack even if it is None
2✔
1874

1875
        else:
1876
            from boa3.internal.neo.vm.CallCode import CallCode
2✔
1877
            call_code = CallCode(function)
2✔
1878
            self.__insert_code(call_code)
2✔
1879
            self._update_codes_with_target(call_code)
2✔
1880

1881
            for arg in range(num_args):
2✔
1882
                self._stack_pop()
2✔
1883
            if function.is_init:
2✔
1884
                self._stack_pop()  # pop duplicated result if it's init
2✔
1885

1886
            if function.return_type is not Type.none:
2✔
1887
                self._stack_append(function.return_type)
2✔
1888

1889
    def convert_method_token_call(self, method_token_id: int):
2✔
1890
        """
1891
        Converts a method token call
1892
        """
1893
        if method_token_id >= 0:
2✔
1894
            method_token = VMCodeMapping.instance().get_method_token(method_token_id)
2✔
1895
            if method_token is not None:
2✔
1896
                opcode_info = OpcodeInfo.CALLT
2✔
1897
                self.__insert1(opcode_info, Integer(method_token_id).to_byte_array(min_length=opcode_info.data_len))
2✔
1898

1899
    def convert_event_call(self, event: Event):
2✔
1900
        """
1901
        Converts an event call
1902

1903
        :param event_id: called event identifier
1904
        :param event: called event
1905
        """
1906
        self.convert_new_array(len(event.args_to_generate), Type.list)
2✔
1907
        if event.generate_name:
2✔
1908
            self.convert_literal(event.name)
2✔
1909
        else:
1910
            self.swap_reverse_stack_items(2)
2✔
1911

1912
        from boa3.internal.model.builtin.interop.interop import Interop
2✔
1913
        self._convert_builtin_call(Interop.Notify, is_internal=True)
2✔
1914

1915
    def convert_class_symbol(self, class_type: ClassType, symbol_id: str, load: bool = True) -> int | None:
2✔
1916
        """
1917
        Converts an class symbol
1918

1919
        :param class_type:
1920
        :param symbol_id:
1921
        :param load:
1922
        """
1923
        method: Method
1924
        is_safe_to_convert = False
2✔
1925

1926
        if symbol_id in class_type.variables:
2✔
1927
            return self.convert_class_variable(class_type, symbol_id, load)
2✔
1928
        elif symbol_id in class_type.properties:
2✔
1929
            symbol = class_type.properties[symbol_id]
2✔
1930
            is_safe_to_convert = True
2✔
1931
            method = symbol.getter if load else symbol.setter
2✔
1932
        elif symbol_id in class_type.instance_methods:
2✔
1933
            method = class_type.instance_methods[symbol_id]
2✔
1934
        elif symbol_id in class_type.class_methods:
2✔
1935
            method = class_type.class_methods[symbol_id]
2✔
1936
        elif isinstance(class_type, UserClass):
2✔
1937
            return self.convert_user_class(class_type, symbol_id)
2✔
1938
        else:
1939
            return
×
1940

1941
        if isinstance(method, IBuiltinMethod):
2✔
1942
            if not is_safe_to_convert:
2✔
1943
                is_safe_to_convert = len(method.args) == 0
2✔
1944

1945
            if is_safe_to_convert:
2✔
1946
                self.convert_builtin_method_call(method)
2✔
1947
        else:
1948
            self.convert_method_call(method, 0)
×
1949
        return symbol_id
2✔
1950

1951
    def convert_user_class(self, class_type: UserClass, symbol_id: str) -> int | None:
2✔
1952
        """
1953
        Converts an class symbol
1954

1955
        :param class_type:
1956
        :param symbol_id:
1957
        """
1958
        start_address = self.bytecode_size
2✔
1959

1960
        if symbol_id in self._statics:
2✔
1961
            self.convert_load_variable(symbol_id, Variable(class_type))
2✔
1962
        else:
1963
            # TODO: change to create an array with the class variables' default values when they are implemented #2kq1vgn
1964
            self.convert_new_empty_array(len(class_type.class_variables), class_type)
2✔
1965

1966
        return start_address
2✔
1967

1968
    def convert_class_variable(self, class_type: ClassType, symbol_id: str, load: bool = True):
2✔
1969
        """
1970
        Converts an class variable
1971

1972
        :param class_type:
1973
        :param symbol_id:
1974
        :param load:
1975
        """
1976
        if symbol_id in class_type.variables:
2✔
1977
            index = list(class_type.variables).index(symbol_id)
2✔
1978

1979
            if load:
2✔
1980
                self.convert_literal(index)
2✔
1981
                self.convert_get_item(index_inserted_internally=True)
2✔
1982

1983
                symbol_type = class_type.variables[symbol_id].type
2✔
1984
                if self._stack[-1] != symbol_type:
2✔
1985
                    self._stack_pop()
2✔
1986
                    self._stack_append(symbol_type)
2✔
1987

1988
            return index
2✔
1989

1990
    def convert_operation(self, operation: IOperation, is_internal: bool = False):
2✔
1991
        """
1992
        Converts an operation
1993

1994
        :param operation: the operation that will be converted
1995
        :param is_internal: whether it was called when generating other implemented symbols
1996
        """
1997
        stack_before = len(self._stack)
2✔
1998
        if is_internal:
2✔
1999
            operation.generate_internal_opcodes(self)
2✔
2000
        else:
2001
            operation.generate_opcodes(self)
2✔
2002

2003
        expected_stack_after = stack_before - operation.op_on_stack
2✔
2004
        if expected_stack_after < 0:
2✔
UNCOV
2005
            expected_stack_after = 0
×
2006

2007
        while expected_stack_after < len(self._stack):
2✔
2008
            self._stack_pop()
2✔
2009
        self._stack_append(operation.result)
2✔
2010

2011
    def convert_assert(self, has_message: bool = False):
2✔
2012

2013
        if has_message:
2✔
2014
            asserted_type = self._stack[-2] if len(self._stack) > 1 else Type.any
2✔
2015
        else:
2016
            asserted_type = self._stack[-1] if len(self._stack) > 0 else Type.any
2✔
2017

2018
        if not isinstance(asserted_type, PrimitiveType):
2✔
2019
            if has_message:
2✔
2020
                self.swap_reverse_stack_items(2)
×
2021

2022
            len_pos = VMCodeMapping.instance().bytecode_size
2✔
2023
            # if the value is an array, a map or a struct, asserts it is not empty
2024
            self.convert_builtin_method_call(Builtin.Len)
2✔
2025
            len_code = VMCodeMapping.instance().code_map[len_pos]
2✔
2026

2027
            if asserted_type is Type.any:
2✔
2028
                # need to check in runtime
2029
                self.duplicate_stack_top_item()
2✔
2030
                self.insert_type_check(StackItemType.Array)
2✔
2031
                self._insert_jump(OpcodeInfo.JMPIF, len_code)
2✔
2032

2033
                self.duplicate_stack_top_item()
2✔
2034
                self.insert_type_check(StackItemType.Map)
2✔
2035
                self._insert_jump(OpcodeInfo.JMPIF, len_code)
2✔
2036

2037
                self.duplicate_stack_top_item()
2✔
2038
                self.insert_type_check(StackItemType.Struct)
2✔
2039
                self._insert_jump(OpcodeInfo.JMPIFNOT, 2)
2✔
2040

2041
                VMCodeMapping.instance().move_to_end(len_pos, len_pos)
2✔
2042
            if has_message:
2✔
2043
                self.swap_reverse_stack_items(2)
×
2044

2045
        self.__insert1(OpcodeInfo.ASSERT if not has_message else OpcodeInfo.ASSERTMSG)
2✔
2046
        if has_message and len(self._stack) > 1:
2✔
2047
            # pop message
2048
            self._stack_pop()
2✔
2049

2050
        if len(self._stack) > 0:
2✔
2051
            # pop assert test
2052
            self._stack_pop()
2✔
2053

2054
    def convert_abort(self, has_message: bool = False, *, is_internal: bool = False):
2✔
2055
        if not is_internal:
2✔
2056
            abort_msg_type = self._stack[-1] if len(self._stack) > 0 else Type.none
2✔
2057
            if has_message and Type.none.is_type_of(abort_msg_type):
2✔
2058
                # do not use ABORTMSG if we can detect the msg is None
2059
                has_message = not has_message
×
2060
                self._stack_pop()
×
2061

2062
        if not has_message:
2✔
2063
            self.__insert1(OpcodeInfo.ABORT)
×
2064
        else:
2065
            self.__insert1(OpcodeInfo.ABORTMSG)
2✔
2066
            self._stack_pop()
2✔
2067

2068
    def convert_new_exception(self, exception_args_len: int = 0):
2✔
2069
        if exception_args_len == 0 or len(self._stack) == 0:
2✔
2070
            self.convert_literal(Builtin.Exception.default_message)
2✔
2071

2072
        if exception_args_len > 1:
2✔
2073
            self.convert_new_array(exception_args_len)
×
2074

2075
        self._stack_pop()
2✔
2076
        self._stack_append(Type.exception)
2✔
2077

2078
    def convert_raise_exception(self):
2✔
2079
        if len(self._stack) == 0:
2✔
2080
            self.convert_literal(Builtin.Exception.default_message)
×
2081

2082
        self._stack_pop()
2✔
2083
        self.__insert1(OpcodeInfo.THROW)
2✔
2084

2085
    def insert_opcode(self, opcode: Opcode, data: bytes = None,
2✔
2086
                      add_to_stack: list[IType] = None, pop_from_stack: bool = False):
2087
        """
2088
        Inserts one opcode into the bytecode. Used to generate built-in symbols.
2089

2090
        :param opcode: info of the opcode  that will be inserted
2091
        :param data: data of the opcode, if needed
2092
        :param add_to_stack: expected data to be included on stack, if needed
2093
        :param pop_from_stack: if needs to update stack given opcode stack items
2094
        """
2095
        op_info = OpcodeInfo.get_info(opcode)
2✔
2096
        if op_info is not None:
2✔
2097
            self.__insert1(op_info, data)
2✔
2098
            if pop_from_stack:
2✔
2099
                for _ in range(op_info.stack_items):
2✔
2100
                    self._stack_pop()
2✔
2101

2102
        if isinstance(add_to_stack, list):
2✔
2103
            for stack_item in add_to_stack:
2✔
2104
                self._stack_append(stack_item)
2✔
2105

2106
    def insert_type_check(self, type_to_check: StackItemType | None):
2✔
2107
        if isinstance(type_to_check, StackItemType):
2✔
2108
            self.__insert1(OpcodeInfo.ISTYPE, type_to_check)
2✔
2109
        else:
2110
            self.__insert1(OpcodeInfo.ISNULL)
2✔
2111
        self._stack_pop()
2✔
2112
        self._stack_append(Type.bool)
2✔
2113

2114
    def __insert1(self, op_info: OpcodeInformation, data: bytes = None):
2✔
2115
        """
2116
        Inserts one opcode into the bytecode
2117

2118
        :param op_info: info of the opcode  that will be inserted
2119
        :param data: data of the opcode, if needed
2120
        """
2121
        vm_code = VMCode(op_info, data)
2✔
2122

2123
        if OpcodeHelper.has_target(op_info.opcode):
2✔
2124
            data = vm_code.raw_data
2✔
2125
            relative_address: int = Integer.from_bytes(data, signed=True)
2✔
2126
            actual_address = VMCodeMapping.instance().bytecode_size + relative_address
2✔
2127
            if (self._can_append_target
2✔
2128
                    and relative_address != 0
2129
                    and actual_address in VMCodeMapping.instance().code_map):
2130
                vm_code.set_target(VMCodeMapping.instance().code_map[actual_address])
2✔
2131
            else:
2132
                self._include_missing_target(vm_code, actual_address)
2✔
2133

2134
        self.__insert_code(vm_code)
2✔
2135
        self._update_codes_with_target(vm_code)
2✔
2136

2137
    def __insert_code(self, vm_code: VMCode):
2✔
2138
        """
2139
        Inserts one vmcode into the bytecode
2140

2141
        :param vm_code: the opcode that will be inserted
2142
        """
2143
        VMCodeMapping.instance().insert_code(vm_code)
2✔
2144

2145
    def _include_missing_target(self, vmcode: VMCode, target_address: int = 0):
2✔
2146
        """
2147
        Includes a instruction which parameter is another instruction that wasn't converted yet
2148

2149
        :param vmcode: instruction with incomplete parameter
2150
        :param target_address: target instruction expected address
2151
        :return:
2152
        """
2153
        if OpcodeHelper.has_target(vmcode.opcode):
2✔
2154
            if target_address == VMCodeMapping.instance().bytecode_size:
2✔
2155
                target_address = None
2✔
2156
            else:
2157
                self._remove_missing_target(vmcode)
2✔
2158

2159
            if target_address not in self._missing_target:
2✔
2160
                self._missing_target[target_address] = []
2✔
2161
            if vmcode not in self._missing_target[target_address]:
2✔
2162
                self._missing_target[target_address].append(vmcode)
2✔
2163

2164
    def _remove_missing_target(self, vmcode: VMCode):
2✔
2165
        """
2166
        Removes a instruction from the missing target list
2167

2168
        :param vmcode: instruction with incomplete parameter
2169
        :return:
2170
        """
2171
        if OpcodeHelper.has_target(vmcode.opcode):
2✔
2172
            for target_address, opcodes in self._missing_target.copy().items():
2✔
2173
                if vmcode in opcodes:
2✔
2174
                    opcodes.remove(vmcode)
2✔
2175
                    if len(opcodes) == 0:
2✔
2176
                        self._missing_target.pop(target_address)
2✔
2177
                    break
2✔
2178

2179
    def _check_codes_with_target(self) -> bool:
2✔
2180
        """
2181
        Verifies if there are any instructions targeting positions not included yet.
2182
        """
2183
        instance = VMCodeMapping.instance()
2✔
2184
        current_bytecode_size = instance.bytecode_size
2✔
2185
        for target_address, codes in list(self._missing_target.items()):
2✔
2186
            if target_address is not None and target_address >= current_bytecode_size:
2✔
2187
                return True
×
2188

2189
        if None in self._missing_target:
2✔
2190
            for code in self._missing_target[None]:
2✔
2191
                if OpcodeHelper.is_jump(code.info.opcode) and code.target is None:
2✔
2192
                    target = Integer.from_bytes(code.raw_data) + VMCodeMapping.instance().get_start_address(code)
2✔
2193
                    if target >= current_bytecode_size:
2✔
2194
                        return True
2✔
2195
        return False
2✔
2196

2197
    def _update_codes_with_target(self, vm_code: VMCode):
2✔
2198
        """
2199
        Verifies if there are any instructions targeting the code. If it exists, updates each instruction found
2200

2201
        :param vm_code: targeted instruction
2202
        """
2203
        instance = VMCodeMapping.instance()
2✔
2204
        vm_code_start_address = instance.get_start_address(vm_code)
2✔
2205
        for target_address, codes in list(self._missing_target.items()):
2✔
2206
            if target_address is not None and target_address <= vm_code_start_address:
2✔
2207
                for code in codes:
2✔
2208
                    code.set_target(vm_code)
2✔
2209
                self._missing_target.pop(target_address)
2✔
2210

2211
    def _update_codes_without_target_to_next(self, address: int = None):
2✔
2212
        if address is None:
2✔
2213
            address = self.bytecode_size
×
2214

2215
        instance = VMCodeMapping.instance()
2✔
2216
        vm_code = instance.get_code(address)
2✔
2217
        if vm_code is None:
2✔
2218
            return
×
2219

2220
        next_address = instance.get_end_address(vm_code)
2✔
2221
        if address in self._missing_target:
2✔
2222
            targets = self._missing_target.pop(address)
×
2223

2224
            if next_address in self._missing_target:
×
2225
                self._missing_target[next_address].extend(targets)
×
2226
            else:
2227
                self._missing_target[next_address] = targets
×
2228

2229
        if None in self._missing_target:
2✔
2230
            for code in self._missing_target[None]:
2✔
2231
                code_address = instance.get_start_address(code)
2✔
2232
                target_address = Integer.from_bytes(code.raw_data) + code_address
2✔
2233
                if target_address == address and target_address != code_address:
2✔
2234
                    data = self._get_jump_data(code.info, next_address - code_address)
2✔
2235
                    instance.update_vm_code(code, code.info, data)
2✔
2236

2237
    def set_code_targets(self):
2✔
2238
        for target, vmcodes in self._missing_target.copy().items():
2✔
2239
            if target is None:
2✔
2240
                for code in vmcodes.copy():
2✔
2241
                    relative_address: int = Integer.from_bytes(code.raw_data, signed=True)
2✔
2242
                    code_address: int = VMCodeMapping.instance().get_start_address(code)
2✔
2243
                    absolute_address = code_address + relative_address
2✔
2244
                    code.set_target(VMCodeMapping.instance().get_code(absolute_address))
2✔
2245

2246
                    vmcodes.remove(code)
2✔
2247
            else:
2248
                for code in vmcodes.copy():
×
2249
                    code.set_target(VMCodeMapping.instance().get_code(target))
×
2250
                    vmcodes.remove(code)
×
2251

2252
            if len(vmcodes) == 0:
2✔
2253
                self._missing_target.pop(target)
2✔
2254

2255
    def _insert_jump(self, op_info: OpcodeInformation, jump_to: int | VMCode = 0, insert_jump: bool = False):
2✔
2256
        """
2257
        Inserts a jump opcode into the bytecode
2258

2259
        :param op_info: info of the opcode  that will be inserted
2260
        :param jump_to: data of the opcode
2261
        :param insert_jump: whether it should be included a jump to the end before the else branch
2262
        """
2263
        if isinstance(jump_to, VMCode):
2✔
2264
            jump_to = VMCodeMapping.instance().get_start_address(jump_to) - VMCodeMapping.instance().bytecode_size
2✔
2265

2266
        if self.last_code.opcode is not Opcode.RET or insert_jump:
2✔
2267
            data: bytes = self._get_jump_data(op_info, jump_to)
2✔
2268
            self.__insert1(op_info, data)
2✔
2269
        for x in range(op_info.stack_items):
2✔
2270
            self._stack_pop()
2✔
2271

2272
    def _update_jump(self, jump_address: int, updated_jump_to: int):
2✔
2273
        """
2274
        Updates the data of a jump code in the bytecode
2275

2276
        :param jump_address: jump code start address
2277
        :param updated_jump_to: new data of the code
2278
        """
2279
        vmcode: VMCode = VMCodeMapping.instance().get_code(jump_address)
2✔
2280
        if vmcode is not None:
2✔
2281
            if updated_jump_to in VMCodeMapping.instance().code_map:
2✔
2282
                self._remove_missing_target(vmcode)
2✔
2283
                target: VMCode = VMCodeMapping.instance().get_code(updated_jump_to)
2✔
2284
                vmcode.set_target(target)
2✔
2285
            else:
2286
                data: bytes = self._get_jump_data(vmcode.info, updated_jump_to - jump_address)
2✔
2287
                VMCodeMapping.instance().update_vm_code(vmcode, vmcode.info, data)
2✔
2288
                if updated_jump_to not in VMCodeMapping.instance().code_map:
2✔
2289
                    self._include_missing_target(vmcode, updated_jump_to)
2✔
2290

2291
    def change_jump(self, jump_address: int, new_jump_opcode: Opcode):
2✔
2292
        """
2293
        Changes the type of jump code in the bytecode
2294

2295
        """
2296
        if not OpcodeHelper.is_jump(new_jump_opcode):
2✔
2297
            return
×
2298

2299
        vmcode: VMCode = VMCodeMapping.instance().get_code(jump_address)
2✔
2300
        if vmcode is not None and OpcodeHelper.is_jump(vmcode.opcode):
2✔
2301
            previous_consumed_items_from_stack = vmcode.info.stack_items
2✔
2302
            new_consumed_items_from_stack = OpcodeInfo.get_info(new_jump_opcode).stack_items
2✔
2303
            if previous_consumed_items_from_stack < new_consumed_items_from_stack:
2✔
2304
                # if previous jump doesn't have condition and the new one has
2305
                previous_stack = self._stack_states.get_state(jump_address)
2✔
2306
                items_to_consume = new_consumed_items_from_stack - previous_consumed_items_from_stack
2✔
2307

2308
                if jump_address == self.last_code_start_address:
2✔
2309
                    for _ in range(items_to_consume):
2✔
2310
                        self._stack_pop()
2✔
2311
                    target_stack_size = len(self._stack)
2✔
2312
                else:
2313
                    target_stack_size = len(previous_stack) - items_to_consume
×
2314

2315
                while len(previous_stack) > target_stack_size:
2✔
2316
                    previous_stack.pop(-1)  # consume last stack item as jump condition
2✔
2317

2318
            vmcode.set_opcode(OpcodeInfo.get_info(new_jump_opcode))
2✔
2319

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

2323
    def _add_to_metadata_permissions(self, contract_class: ContractInterfaceClass, method_name: str):
2✔
2324
        from boa3.internal.compiler.compiledmetadata import CompiledMetadata
2✔
2325
        CompiledMetadata.instance().add_contract_permission(contract_class.contract_hash, method_name)
2✔
2326

2327
    def duplicate_stack_top_item(self):
2✔
2328
        self.duplicate_stack_item(1)
2✔
2329

2330
    def duplicate_stack_item(self, pos: int = 0, *, expected_stack_item: IType = None):
2✔
2331
        """
2332
        Duplicates the item n back in the stack
2333

2334
        :param pos: index of the variable
2335
        """
2336
        # n = 1 -> duplicates stack top item
2337
        # n = 0 -> value varies in runtime
2338
        if pos >= 0:
2✔
2339
            opcode: Opcode = OpcodeHelper.get_dup(pos)
2✔
2340
            if opcode is Opcode.PICK:
2✔
2341
                if pos > 0:
2✔
2342
                    self.convert_literal(pos - 1)
2✔
2343
                    self._stack_pop()
2✔
2344
                elif Type.int.is_type_of(self._stack[-1]) and expected_stack_item is not None:
2✔
2345
                    self._stack_pop()
2✔
2346

2347
            op_info = OpcodeInfo.get_info(opcode)
2✔
2348
            self.__insert1(op_info)
2✔
2349
            if expected_stack_item is None:
2✔
2350
                stacked_value = self._stack[-pos]
2✔
2351
            else:
2352
                stacked_value = expected_stack_item
2✔
2353

2354
            self._stack_append(stacked_value)
2✔
2355

2356
    def move_stack_item_to_top(self, pos: int = 0):
2✔
2357
        """
2358
        Moves the item n back in the stack to the top
2359

2360
        :param pos: index of the variable
2361
        """
2362
        # n = 1 -> stack top item
2363
        # n = 0 -> value varies in runtime
2364
        # do nothing in those cases
2365
        if pos == 2:
2✔
2366
            self.swap_reverse_stack_items(2)
×
2367
        elif pos > 2:
2✔
2368
            self.convert_literal(pos - 1)
2✔
2369
            self._stack_pop()
2✔
2370
            self.__insert1(OpcodeInfo.ROLL)
2✔
2371
            stack_type = self._stack_pop(-pos)
2✔
2372
            self._stack_append(stack_type)
2✔
2373

2374
    def clear_stack(self, clear_if_in_loop: bool = False):
2✔
2375
        if not clear_if_in_loop or len(self._current_for) > 0:
2✔
2376
            for _ in range(self.stack_size):
2✔
2377
                self.__insert1(OpcodeInfo.DROP)
2✔
2378

2379
    def remove_stack_top_item(self):
2✔
2380
        self.remove_stack_item(1)
2✔
2381

2382
    def remove_stack_item(self, pos: int = 0):
2✔
2383
        """
2384
        Removes the item n from the stack
2385

2386
        :param pos: index of the variable
2387
        """
2388
        # n = 1 -> removes stack top item
2389
        if pos > 0:
2✔
2390
            opcode: Opcode = OpcodeHelper.get_drop(pos)
2✔
2391
            if opcode is Opcode.XDROP:
2✔
2392
                self.convert_literal(pos - 1)
2✔
2393
                self._stack_pop()
2✔
2394
            op_info = OpcodeInfo.get_info(opcode)
2✔
2395
            self.__insert1(op_info)
2✔
2396
            if pos > 0 and len(self._stack) > 0:
2✔
2397
                self._stack_pop(-pos)
2✔
2398

2399
    def _remove_inserted_opcodes_since(self, last_address: int, last_stack_size: int | None = None):
2✔
2400
        self._stack_states.restore_state(last_address)
2✔
2401
        if VMCodeMapping.instance().bytecode_size > last_address:
2✔
2402
            # remove opcodes inserted during the evaluation of the symbol
2403
            VMCodeMapping.instance().remove_opcodes(last_address, VMCodeMapping.instance().bytecode_size)
2✔
2404

2405
        if isinstance(last_stack_size, int) and last_stack_size < self.stack_size:
2✔
2406
            # remove any additional values pushed to the stack during the evalution of the symbol
2407
            for _ in range(self.stack_size - last_stack_size):
2✔
2408
                self._stack_pop()
2✔
2409

2410
    def swap_reverse_stack_items(self, no_items: int = 0, rotate: bool = False):
2✔
2411
        # n = 0 -> value varies in runtime
2412
        if 0 <= no_items != 1:
2✔
2413
            opcode: Opcode = OpcodeHelper.get_reverse(no_items, rotate)
2✔
2414
            if opcode is Opcode.REVERSEN and no_items > 0:
2✔
2415
                self.convert_literal(no_items)
2✔
2416
            op_info = OpcodeInfo.get_info(opcode)
2✔
2417
            self.__insert1(op_info)
2✔
2418
            if opcode is Opcode.REVERSEN and no_items > 0:
2✔
2419
                self._stack_pop()
2✔
2420
            if no_items > 0:
2✔
2421
                self._stack_reverse(-no_items, rotate=rotate)
2✔
2422

2423
    def convert_init_user_class(self, class_type: ClassType):
2✔
2424
        if isinstance(class_type, UserClass):
2✔
2425
            # create an none-filled array with the size of the instance variables
2426
            no_instance_variables = len(class_type.instance_variables)
2✔
2427
            self.convert_new_empty_array(no_instance_variables, class_type)
2✔
2428

2429
            self.__insert1(OpcodeInfo.UNPACK)  # unpack for array concatenation
2✔
2430
            value = self._stack_pop()
2✔
2431
            self._stack_append(Type.int)
2✔
2432
            self.remove_stack_top_item()
2✔
2433

2434
            # copy the array that stores the class variables from that class
2435
            self.convert_user_class(class_type, class_type.identifier)
2✔
2436

2437
            self.__insert1(OpcodeInfo.UNPACK)  # unpack for array concatenation
2✔
2438
            self._stack_append(value)
2✔
2439
            self.convert_literal(no_instance_variables)
2✔
2440
            self.convert_operation(BinaryOp.Add)
2✔
2441

2442
            self.__insert1(OpcodeInfo.PACK)  # packs everything together
2✔
2443
            self._stack_pop()
2✔
2444

2445
    def generate_implicit_init_user_class(self, init_method: Method):
2✔
2446
        if not init_method.is_called:
2✔
2447
            return
2✔
2448

2449
        self.convert_begin_method(init_method)
2✔
2450
        class_type = init_method.return_type
2✔
2451
        for base in class_type.bases:
2✔
2452
            base_constructor = base.constructor_method()
2✔
2453
            num_args = len(base_constructor.args)
2✔
2454

2455
            for arg_id, arg_var in reversed(list(init_method.args.items())):
2✔
2456
                self.convert_load_variable(arg_id, arg_var)
2✔
2457

2458
            args_to_call = num_args - 1 if num_args > 0 else num_args
2✔
2459
            self.convert_method_call(base_constructor, args_to_call)
2✔
2460
            self.remove_stack_top_item()
2✔
2461

2462
        self.convert_end_method()
2✔
2463

2464
    def compare_dicts_match_case(self):
2✔
2465
        # stack: MATCH var (bottom), CASE pattern (top)
2466
        self.swap_reverse_stack_items(2)
2✔
2467
        self.duplicate_stack_top_item()
2✔
2468
        # pattern_keys = pattern.keys()
2469
        self.convert_builtin_method_call(Builtin.DictKeys)
2✔
2470
        # stack: var, pattern, pattern_keys
2471

2472
        self.duplicate_stack_top_item()
2✔
2473
        # list_length = len(pattern_keys)
2474
        self.convert_builtin_method_call(Builtin.Len)
2✔
2475
        # index = 0
2476
        self.convert_literal(0)
2✔
2477
        # is_ok = True
2478
        self.convert_literal(True)
2✔
2479
        # stack: var, pattern, pattern_keys, list_length, index, is_ok
2480

2481
        # while index < list_length and is_ok:
2482
        begin_map_comparison = self.convert_begin_while()
2✔
2483

2484
        self.duplicate_stack_item(4)
2✔
2485
        self.duplicate_stack_item(3)
2✔
2486
        # stack: var, pattern, pattern_keys, list_length, index, is_ok, pattern_keys, index
2487
        self.convert_get_item(index_inserted_internally=True, index_is_positive=True, test_is_negative_index=False)
2✔
2488
        # current_key = pattern_keys[index]
2489
        # stack: var, pattern, pattern_keys, list_length, index, is_ok, current_key
2490

2491
        self.duplicate_stack_top_item()
2✔
2492
        self.duplicate_stack_item(8)
2✔
2493
        self.swap_reverse_stack_items(2)
2✔
2494
        # stack: var, pattern, pattern_keys, list_length, index, is_ok, current_key, var, current_key
2495

2496
        self.swap_reverse_stack_items(2)
2✔
2497
        # key_in_var = current_key in var
2498
        self.convert_operation(BinaryOp.In, is_internal=True)
2✔
2499
        # stack: var, pattern, pattern_keys, list_length, index, is_ok, current_key, key_in_var
2500

2501
        #   if key_in_var:
2502
        if_key_in_var = self.convert_begin_if()
2✔
2503
        self.duplicate_stack_top_item()
2✔
2504
        self.duplicate_stack_item(8)
2✔
2505
        self.swap_reverse_stack_items(2)
2✔
2506
        # stack: var, pattern, pattern_keys, list_length, index, is_ok, current_key, var, current_key
2507

2508
        #     var_current_value == var[current_key]
2509
        self.convert_get_item(index_inserted_internally=True, index_is_positive=True, test_is_negative_index=False)
2✔
2510
        # stack: var, pattern, pattern_keys, list_length, index, is_ok, current_key, var_current_value
2511
        self.swap_reverse_stack_items(2)
2✔
2512
        self.duplicate_stack_item(7)
2✔
2513
        self.swap_reverse_stack_items(2)
2✔
2514
        # stack: var, pattern, pattern_keys, list_length, index, is_ok, var_current_value, pattern, current_key
2515
        #     pattern_current_value == pattern[current_key]
2516
        self.convert_get_item(index_inserted_internally=True, index_is_positive=True, test_is_negative_index=False)
2✔
2517
        # stack: var, pattern, pattern_keys, list_length, index, is_ok, var_current_value, pattern_current_value
2518
        #     not_same_values = var_current_value == pattern_current_value
2519
        self.convert_operation(BinaryOp.NotEq, is_internal=True)
2✔
2520
        # stack: var, pattern, pattern_keys, list_length, index, is_ok, not_same_values
2521

2522
        #     if not_same_values:
2523
        values_are_not_equal = self.convert_begin_if()
2✔
2524
        self.remove_stack_top_item()
2✔
2525
        #       return False
2526
        self.convert_literal(False)
2✔
2527

2528
        #   else:
2529
        self.convert_begin_else(if_key_in_var)
2✔
2530
        self.remove_stack_top_item()
2✔
2531
        self.remove_stack_top_item()
2✔
2532
        #     return False
2533
        self.convert_literal(False)
2✔
2534

2535
        self.convert_end_if(values_are_not_equal, is_internal=True)
2✔
2536

2537
        #  index += 1
2538
        self.swap_reverse_stack_items(2)
2✔
2539
        self.__insert1(OpcodeInfo.INC)
2✔
2540
        self.swap_reverse_stack_items(2)
2✔
2541

2542
        # stack: var, pattern, pattern_keys, list_length, index, is_ok
2543
        # checking if `index < list_length and is_ok` is still true
2544
        condition_address = self.bytecode_size
2✔
2545
        self.duplicate_stack_item(2)
2✔
2546
        self.duplicate_stack_item(4)
2✔
2547
        self.convert_operation(BinaryOp.Lt, is_internal=True)
2✔
2548
        self.duplicate_stack_item(2)
2✔
2549
        self.convert_operation(BinaryOp.And, is_internal=True)
2✔
2550
        self.convert_end_while(begin_map_comparison, condition_address, is_internal=True)
2✔
2551

2552
        # when finishing while loop, clean the stack and only `is_ok` will remain
2553
        self.remove_stack_item(2)
2✔
2554
        self.remove_stack_item(2)
2✔
2555
        self.remove_stack_item(2)
2✔
2556
        self.remove_stack_item(2)
2✔
2557
        self.remove_stack_item(2)
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

© 2026 Coveralls, Inc