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

CityOfZion / neo3-boa / cdd4f175-bfea-4f3d-8a74-6ee34db45e69

10 Apr 2024 03:28PM UTC coverage: 92.021% (-0.002%) from 92.023%
cdd4f175-bfea-4f3d-8a74-6ee34db45e69

push

circleci

Mirella de Medeiros
CU-86drpncat - Wrong tuple type hint is compiled on Neo3-boa

107 of 118 new or added lines in 8 files covered. (90.68%)

65 existing lines in 6 files now uncovered.

20908 of 22721 relevant lines covered (92.02%)

1.84 hits per line

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

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

5

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

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

53

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

125
                visitor.global_stmts = static_stmts
2✔
126

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

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

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

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

147
        except CompilerError:
×
148
            pass
×
149

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

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

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

172
        return imports
2✔
173

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

271
    def _stack_append(self, value_type: IType):
2✔
272
        self._stack_states.append(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:
UNCOV
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:
UNCOV
421
                                var_index = original_ids.index(value)
×
UNCOV
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✔
UNCOV
431
                        additional_items.append(var_id)
×
UNCOV
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✔
UNCOV
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✔
UNCOV
479
                if identifier in scope.symbols and isinstance(scope.symbols[identifier], ISymbol):
×
UNCOV
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✔
UNCOV
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
×
UNCOV
510
                            found_symbol = imports.builtin.get_internal_symbol(identifier)
×
UNCOV
511
                            if isinstance(found_symbol, ISymbol):
×
UNCOV
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✔
UNCOV
519
                        found_id = static_id
×
UNCOV
520
                        break
×
521

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

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

529
        :return: whether there are static fields to be initialized
530
        """
531
        if not self.can_init_static_fields:
2✔
532
            return False
2✔
533
        if self.initialized_static_fields:
2✔
UNCOV
534
            return False
×
535

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

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

547
            init_method = Method(is_public=True)
2✔
548
            init_method.init_bytecode = self.last_code
2✔
549
            self.symbol_table[constants.INITIALIZE_METHOD_ID] = init_method
2✔
550

551
        return num_static_fields > 0
2✔
552

553
    def end_initialize(self):
2✔
554
        """
555
        Converts the signature of the method
556
        """
557
        self.insert_return()
2✔
558
        self.initialized_static_fields = True
2✔
559

560
        if constants.INITIALIZE_METHOD_ID in self.symbol_table:
2✔
561
            init_method = self.symbol_table[constants.INITIALIZE_METHOD_ID]
2✔
562
            init_method.end_bytecode = self.last_code
2✔
563

564
    def convert_begin_method(self, method: Method):
2✔
565
        """
566
        Converts the signature of the method
567

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

573
        num_args: int = len(method.args)
2✔
574
        num_vars: int = len(method.locals)
2✔
575

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

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

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

597
            self.insert_return()
2✔
598

599
        self._current_method.end_bytecode = self.last_code
2✔
600
        self._current_method = None
2✔
601
        self._stack.clear()
2✔
602

603
        function_variable_scope = self._scope_stack.pop()
2✔
604

605
    def insert_return(self):
2✔
606
        """
607
        Insert the return statement
608
        """
609
        self.__insert1(OpcodeInfo.RET)
2✔
610

611
    def insert_not(self):
2✔
612
        """
613
        Insert a `not` to change the value of a bool
614
        """
UNCOV
615
        self.__insert1(OpcodeInfo.NOT)
×
616

617
    def insert_nop(self):
2✔
618
        """
619
        Insert a NOP opcode
620
        """
621
        self.__insert1(OpcodeInfo.NOP)
2✔
622

623
    def insert_sys_call(self, sys_call_id: bytes):
2✔
624
        """
625
        Insert a SYSCALL opcode call
626
        """
627
        self.__insert1(OpcodeInfo.SYSCALL, sys_call_id)
2✔
628

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

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

639
        start_address = self.last_code_start_address
2✔
640
        self._current_loop.append(start_address)
2✔
641
        if is_for:
2✔
642
            self._current_for.append(start_address)
2✔
643

644
        return start_address
2✔
645

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

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

656
    def convert_begin_for(self) -> int:
2✔
657
        """
658
        Converts the beginning of the for statement
659

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

668
        address = self.convert_begin_while(True)
2✔
669

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

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

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

693
        for_increment = self.last_code_start_address
2✔
694
        test_address = VMCodeMapping.instance().bytecode_size
2✔
695
        self._update_continue_jumps(start_address, for_increment)
2✔
696

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

706
        self.convert_end_loop(start_address, test_address, True, is_internal=is_internal)
2✔
707

708
        return test_address
2✔
709

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

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

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

729
        self._current_loop.pop()
2✔
730

731
        is_break_pos = self.bytecode_size
2✔
732
        self.convert_literal(False)  # is not break
2✔
733
        is_break_end = self.last_code_start_address
2✔
734
        self._update_break_jumps(start_address)
2✔
735

736
        if is_for:
2✔
737
            self._current_for.pop()
2✔
738
            reverse_to_drop_pos = self.last_code_start_address
2✔
739
            self.swap_reverse_stack_items(3)
2✔
740
            reverse_to_drop_end = self.last_code_start_address
2✔
741

742
            self.remove_stack_top_item()    # removes index and sequence from stack
2✔
743
            self.remove_stack_top_item()
2✔
744

745
            self._insert_loop_break_addresses(start_address, reverse_to_drop_pos, reverse_to_drop_end, self.bytecode_size)
2✔
746

747
        last_opcode = self.bytecode_size
2✔
748
        self._insert_loop_break_addresses(start_address, is_break_pos, is_break_end, last_opcode)
2✔
749
        self._insert_jump(OpcodeInfo.JMPIF)
2✔
750

751
        if is_internal:
2✔
752
            self.convert_end_loop_else(start_address, self.last_code_start_address)
2✔
753
        return last_opcode
2✔
754

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

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

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

782
    def convert_begin_if(self) -> int:
2✔
783
        """
784
        Converts the beginning of the if statement
785

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

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

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

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

808
        return self.last_code_start_address
2✔
809

810
    def convert_end_if(self, start_address: int, is_internal: bool = False):
2✔
811
        """
812
        Converts the end of the if statement
813

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

821
    def convert_begin_try(self) -> int:
2✔
822
        """
823
        Converts the beginning of the try statement
824

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

830
        return self.last_code_start_address
2✔
831

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

836
        :param exception_id: the name identifier of the exception
837
        :type exception_id: str or None
838

839
        :return: the last address from try body
840
        """
841
        self._insert_jump(OpcodeInfo.JMP)
2✔
842
        last_try_code = self.last_code_start_address
2✔
843

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

848
        return last_try_code
2✔
849

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

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

865
            try_vm_code = vmcode_mapping_instance.get_code(start_address)
2✔
866
            try_jump = vmcode_mapping_instance.get_code(end_address)
2✔
867

868
            except_start_address = vmcode_mapping_instance.get_end_address(try_jump) + 1
2✔
869
            except_start_code = vmcode_mapping_instance.get_code(except_start_address)
2✔
870

871
            if isinstance(try_vm_code, TryCode):
2✔
872
                try_vm_code.set_except_code(except_start_code)
2✔
873
            self._update_jump(else_address if else_address is not None else end_address, self.last_code_start_address)
2✔
874

875
        return self.last_code_start_address
2✔
876

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

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

890
            try_vm_code = vmcode_mapping_instance.get_code(start_address)
2✔
891
            try_last_code = vmcode_mapping_instance.get_code(last_address)
2✔
892

893
            finally_start_address = vmcode_mapping_instance.get_end_address(try_last_code) + 1
2✔
894
            finally_start_code = vmcode_mapping_instance.get_code(finally_start_address)
2✔
895

896
            if isinstance(try_vm_code, TryCode):
2✔
897
                try_vm_code.set_finally_code(finally_start_code)
2✔
898
            self._update_jump(vmcode_mapping_instance.bytecode_size, self.last_code_start_address)
2✔
899

900
        self._update_jump(last_address, VMCodeMapping.instance().bytecode_size)
2✔
901

902
    def fix_negative_index(self, value_index: int = None, test_is_negative=True):
2✔
903
        self._can_append_target = not self._can_append_target
2✔
904

905
        value_code = self.last_code_start_address
2✔
906
        size = VMCodeMapping.instance().bytecode_size
2✔
907

908
        if test_is_negative:
2✔
909
            self.duplicate_stack_top_item()
2✔
910
            self.__insert1(OpcodeInfo.SIGN)
2✔
911
            self.convert_literal(-1)
2✔
912

913
            jmp_address = VMCodeMapping.instance().bytecode_size
2✔
914
            self._insert_jump(OpcodeInfo.JMPNE)     # if index < 0
2✔
915

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

924
        if index_of_last >= 0:
2✔
925
            pos_from_top = len(state) - index_of_last
2✔
926
        else:
927
            pos_from_top = 2
2✔
928

929
        self.duplicate_stack_item(pos_from_top)     # index += len(array)
2✔
930
        self.convert_builtin_method_call(Builtin.Len)
2✔
931
        self.convert_operation(BinaryOp.Add)
2✔
932

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

939
            VMCodeMapping.instance().move_to_end(value_index, value_code)
2✔
940

941
        self._can_append_target = not self._can_append_target
2✔
942

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

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

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

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

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

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

988
    def convert_loop_continue(self):
2✔
989
        loop_start = self._current_loop[-1]
2✔
990
        self._insert_jump(OpcodeInfo.JMP)
2✔
991
        continue_address = self.last_code_start_address
2✔
992

993
        if loop_start not in self._jumps_to_loop_condition:
2✔
994
            self._jumps_to_loop_condition[loop_start] = [continue_address]
2✔
995
        else:
UNCOV
996
            self._jumps_to_loop_condition[loop_start].append(continue_address)
×
997

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

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

1013
        self._insert_loop_break_addresses(loop_start, is_break_pos, is_break_end, break_address)
2✔
1014

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

1021
        is_break_instructions = VMCodeMapping.instance().get_addresses(is_break_start, is_break_end)
2✔
1022

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

1032
    def _update_break_jumps(self, loop_start_address) -> int:
2✔
1033
        jump_target = VMCodeMapping.instance().bytecode_size
2✔
1034

1035
        if loop_start_address in self._jumps_to_loop_break:
2✔
1036
            jump_addresses = self._jumps_to_loop_break.pop(loop_start_address)
2✔
1037
            for address in jump_addresses:
2✔
1038
                self._update_jump(address, jump_target)
2✔
1039

1040
    def convert_literal(self, value: Any) -> int:
2✔
1041
        """
1042
        Converts a literal value
1043

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

1067
    def convert_integer_literal(self, value: int):
2✔
1068
        """
1069
        Converts an integer literal value
1070

1071
        :param value: the value to be converted
1072
        """
1073
        opcode = OpcodeHelper.get_literal_push(value)
2✔
1074
        if opcode is not None:
2✔
1075
            op_info: OpcodeInformation = OpcodeInfo.get_info(opcode)
2✔
1076
            self.__insert1(op_info)
2✔
1077
            self._stack_append(Type.int)
2✔
1078
        else:
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
                self.convert_operation(UnaryOp.Negative)
2✔
1085
            else:
1086
                opcode, data = OpcodeHelper.get_push_and_data(value)
2✔
1087
                op_info: OpcodeInformation = OpcodeInfo.get_info(opcode)
2✔
1088
                self.__insert1(op_info, data)
2✔
1089
                self._stack_append(Type.int)
2✔
1090

1091
    def convert_string_literal(self, value: str):
2✔
1092
        """
1093
        Converts an string literal value
1094

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

1101
    def convert_bool_literal(self, value: bool):
2✔
1102
        """
1103
        Converts an boolean literal value
1104

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

1113
    def convert_sequence_literal(self, sequence: Sequence):
2✔
1114
        """
1115
        Converts a sequence value
1116

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

1124
        for inner_value in reversed(sequence):
2✔
1125
            self.convert_literal(inner_value)
2✔
1126

1127
        self.convert_new_array(len(sequence), value_type)
2✔
1128

1129
    def convert_dict_literal(self, dictionary: dict):
2✔
1130
        """
1131
        Converts a dict value
1132

1133
        :param dictionary: the value to be converted
1134
        """
1135
        value_type = Type.dict.build(dictionary)
2✔
1136
        self.convert_new_map(value_type)
2✔
1137

1138
        for key, value in dictionary.items():
2✔
1139
            self.duplicate_stack_top_item()
2✔
1140
            self.convert_literal(key)
2✔
1141
            value_start = self.convert_literal(value)
2✔
1142
            self.convert_set_item(value_start)
2✔
1143

1144
    def convert_byte_array(self, array: bytes):
2✔
1145
        """
1146
        Converts a byte value
1147

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

1154
    def insert_push_data(self, data: bytes):
2✔
1155
        """
1156
        Inserts a push data value
1157

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

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

1172
    def insert_none(self):
2✔
1173
        """
1174
        Converts None literal
1175
        """
1176
        self.__insert1(OpcodeInfo.PUSHNULL)
2✔
1177
        self._stack_append(Type.none)
2✔
1178

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

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

1192
            # but changes the value internally
1193
            self._stack_pop()
2✔
1194
            self._stack_append(value_type)
2✔
1195

1196
    def convert_new_map(self, map_type: IType):
2✔
1197
        """
1198
        Converts the creation of a new map
1199

1200
        :param map_type: the Neo Boa type of the map
1201
        """
1202
        self.__insert1(OpcodeInfo.NEWMAP)
2✔
1203
        self._stack_append(map_type)
2✔
1204

1205
    def convert_new_empty_array(self, length: int, array_type: IType):
2✔
1206
        """
1207
        Converts the creation of a new empty array
1208

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

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

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

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

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

1256
        self.__insert1(OpcodeInfo.SETITEM)
2✔
1257
        self._stack_pop()  # value
2✔
1258
        self._stack_pop()  # index
2✔
1259
        self._stack_pop()  # array or map
2✔
1260

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

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

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

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

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

1300
            self._insert_jump(OpcodeInfo.JMPIF)
2✔
1301
            jmp_address = self.last_code_start_address
2✔
1302
            self.remove_stack_top_item()
2✔
1303
            self.convert_literal(0)
2✔
1304

1305
        self._stack_pop()  # length
2✔
1306
        self._stack_pop()  # start
2✔
1307
        original = self._stack_pop()  # original string
2✔
1308

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

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

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

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

1336
        self.__insert1(OpcodeInfo.INC)              # index += 1
2✔
1337

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

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

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

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

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

1382
                self.convert_literal(0)
2✔
1383
                self.swap_reverse_stack_items(2)
2✔
1384
                self.convert_get_array_slice(array)
2✔
1385

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

1406
                self.duplicate_stack_item(2)
2✔
1407
                self.convert_builtin_method_call(Builtin.Len)
2✔
1408

1409
                self.convert_get_array_slice(array)
2✔
1410

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

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

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

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

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

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

1458
            # remove auxiliary values
1459
            self.swap_reverse_stack_items(3)
2✔
1460
            self.remove_stack_top_item()
2✔
1461
            self.remove_stack_top_item()
2✔
1462

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

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

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

1492
        # increment the index by 1
1493
        self.swap_reverse_stack_items(2)
2✔
1494
        self.__insert1(OpcodeInfo.INC)                  # index++
2✔
1495
        self.swap_reverse_stack_items(2)
2✔
1496

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

1505
        # removing auxiliary values
1506
        self.swap_reverse_stack_items(5)
2✔
1507
        self.remove_stack_top_item()
2✔
1508
        self.remove_stack_top_item()
2✔
1509
        self.remove_stack_top_item()
2✔
1510
        self.remove_stack_top_item()
2✔
1511

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

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

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

1536
        # decrement 1 from index
1537
        self.__insert1(OpcodeInfo.DEC)
2✔
1538

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

1548
        # removing auxiliary values
1549
        self.remove_stack_top_item()                    # removed index from stack
2✔
1550
        self.remove_stack_top_item()                    # removed stride from stack
2✔
1551

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

1561
        self.convert_cast(Type.tuple)
2✔
1562

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

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

1573
        if class_type is not None and symbol_id in class_type.symbols:
2✔
1574
            symbol = class_type.symbols[symbol_id]
2✔
1575

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

1583
            if not params_addresses:
2✔
1584
                params_addresses = []
2✔
1585

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

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

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

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

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

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

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

1635
        elif var_id in self._globals:
2✔
1636
            another_var_id, var = self.get_symbol(var_id)
2✔
1637
            storage_key = codegenerator.get_storage_key_for_variable(var)
2✔
1638
            self._convert_builtin_storage_get_or_put(True, storage_key)
2✔
1639

1640
        elif var.is_global:
2✔
1641
            if not self.store_constant_variable(var):
2✔
1642
                self.convert_literal(var._first_assign_value)
2✔
1643

1644
        elif class_type:
2✔
1645
            self.convert_load_class_variable(class_type, var_id)
2✔
1646

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

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

1656
        if user_class is None and len(self._stack) > 1 and isinstance(self._stack[-2], UserClass):
2✔
1657
            user_class = self._stack[-2]
2✔
1658

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

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

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

1675
            self.swap_reverse_stack_items(no_stack_items_to_swap)
2✔
1676
            self.convert_set_item(index_address, index_inserted_internally=True)
2✔
1677
            return
2✔
1678

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

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

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

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

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

1712
        self.convert_literal(storage_key)
2✔
1713
        self.convert_builtin_method_call(Interop.StorageGetContext)
2✔
1714

1715
        builtin_method = Interop.StorageGet if is_get else Interop.StoragePut
2✔
1716
        self.convert_builtin_method_call(builtin_method)
2✔
1717

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

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

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

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

1748
        if scope is not None:
2✔
1749
            index: int = scope.index(var_id) if var_id in scope else -1
2✔
1750
        else:
1751
            index = -1
2✔
1752

1753
        return index, local, is_arg
2✔
1754

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

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

1769
        if function.pack_arguments:
2✔
UNCOV
1770
            self.convert_new_array(len(args_address))
×
1771

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

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

1785
            if function.push_self_first():
2✔
1786
                addresses = args_end_addresses[:1] + list(reversed(args_end_addresses[1:]))
2✔
1787
            else:
UNCOV
1788
                addresses = list(reversed(args_end_addresses))
×
1789

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

1794
        self._convert_builtin_call(function, previous_stack_size=stack_before, is_internal=is_internal)
2✔
1795

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

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

1809
        size_before_generating = self.bytecode_size
2✔
1810
        if is_internal:
2✔
1811
            builtin.generate_internal_opcodes(self)
2✔
1812
        else:
1813
            builtin.generate_opcodes(self)
2✔
1814

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

1823
        if expected_stack_after < 0:
2✔
UNCOV
1824
            expected_stack_after = 0
×
1825

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

1831
    def convert_method_call(self, function: Method, num_args: int):
2✔
1832
        """
1833
        Converts a method function call
1834

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

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

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

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

1868
        else:
1869
            from boa3.internal.neo.vm.CallCode import CallCode
2✔
1870
            call_code = CallCode(function)
2✔
1871
            self.__insert_code(call_code)
2✔
1872
            self._update_codes_with_target(call_code)
2✔
1873

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

1879
            if function.return_type is not Type.none:
2✔
1880
                self._stack_append(function.return_type)
2✔
1881

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

1892
    def convert_event_call(self, event: Event):
2✔
1893
        """
1894
        Converts an event call
1895

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

1905
        from boa3.internal.model.builtin.interop.interop import Interop
2✔
1906
        self._convert_builtin_call(Interop.Notify, is_internal=True)
2✔
1907

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

1912
        :param class_type:
1913
        :param symbol_id:
1914
        :param load:
1915
        """
1916
        method: Method
1917
        is_safe_to_convert = False
2✔
1918

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

1934
        if isinstance(method, IBuiltinMethod):
2✔
1935
            if not is_safe_to_convert:
2✔
1936
                is_safe_to_convert = len(method.args) == 0
2✔
1937

1938
            if is_safe_to_convert:
2✔
1939
                self.convert_builtin_method_call(method)
2✔
1940
        else:
UNCOV
1941
            self.convert_method_call(method, 0)
×
1942
        return symbol_id
2✔
1943

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

1948
        :param class_type:
1949
        :param symbol_id:
1950
        """
1951
        start_address = self.bytecode_size
2✔
1952

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

1959
        return start_address
2✔
1960

1961
    def convert_class_variable(self, class_type: ClassType, symbol_id: str, load: bool = True):
2✔
1962
        """
1963
        Converts an class variable
1964

1965
        :param class_type:
1966
        :param symbol_id:
1967
        :param load:
1968
        """
1969
        if symbol_id in class_type.variables:
2✔
1970
            index = list(class_type.variables).index(symbol_id)
2✔
1971

1972
            if load:
2✔
1973
                self.convert_literal(index)
2✔
1974
                self.convert_get_item(index_inserted_internally=True)
2✔
1975

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

1981
            return index
2✔
1982

1983
    def convert_operation(self, operation: IOperation, is_internal: bool = False):
2✔
1984
        """
1985
        Converts an operation
1986

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

1996
        expected_stack_after = stack_before - operation.op_on_stack
2✔
1997
        if expected_stack_after < 0:
2✔
UNCOV
1998
            expected_stack_after = 0
×
1999

2000
        while expected_stack_after < len(self._stack):
2✔
2001
            self._stack_pop()
2✔
2002
        self._stack_append(operation.result)
2✔
2003

2004
    def convert_assert(self, has_message: bool = False):
2✔
2005

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

2011
        if not isinstance(asserted_type, PrimitiveType):
2✔
2012
            if has_message:
2✔
UNCOV
2013
                self.swap_reverse_stack_items(2)
×
2014

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

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

2026
                self.duplicate_stack_top_item()
2✔
2027
                self.insert_type_check(StackItemType.Map)
2✔
2028
                self._insert_jump(OpcodeInfo.JMPIF, len_code)
2✔
2029

2030
                self.duplicate_stack_top_item()
2✔
2031
                self.insert_type_check(StackItemType.Struct)
2✔
2032
                self._insert_jump(OpcodeInfo.JMPIFNOT, 2)
2✔
2033

2034
                VMCodeMapping.instance().move_to_end(len_pos, len_pos)
2✔
2035
            if has_message:
2✔
UNCOV
2036
                self.swap_reverse_stack_items(2)
×
2037

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

2043
        if len(self._stack) > 0:
2✔
2044
            # pop assert test
2045
            self._stack_pop()
2✔
2046

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

2055
        if not has_message:
2✔
UNCOV
2056
            self.__insert1(OpcodeInfo.ABORT)
×
2057
        else:
2058
            self.__insert1(OpcodeInfo.ABORTMSG)
2✔
2059
            self._stack_pop()
2✔
2060

2061
    def convert_new_exception(self, exception_args_len: int = 0):
2✔
2062
        if exception_args_len == 0 or len(self._stack) == 0:
2✔
2063
            self.convert_literal(Builtin.Exception.default_message)
2✔
2064

2065
        if exception_args_len > 1:
2✔
UNCOV
2066
            self.convert_new_array(exception_args_len)
×
2067

2068
        self._stack_pop()
2✔
2069
        self._stack_append(Type.exception)
2✔
2070

2071
    def convert_raise_exception(self):
2✔
2072
        if len(self._stack) == 0:
2✔
UNCOV
2073
            self.convert_literal(Builtin.Exception.default_message)
×
2074

2075
        self._stack_pop()
2✔
2076
        self.__insert1(OpcodeInfo.THROW)
2✔
2077

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

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

2095
        if isinstance(add_to_stack, list):
2✔
2096
            for stack_item in add_to_stack:
2✔
2097
                self._stack_append(stack_item)
2✔
2098

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

2107
    def __insert1(self, op_info: OpcodeInformation, data: bytes = None):
2✔
2108
        """
2109
        Inserts one opcode into the bytecode
2110

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

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

2127
        self.__insert_code(vm_code)
2✔
2128
        self._update_codes_with_target(vm_code)
2✔
2129

2130
    def __insert_code(self, vm_code: VMCode):
2✔
2131
        """
2132
        Inserts one vmcode into the bytecode
2133

2134
        :param vm_code: the opcode that will be inserted
2135
        """
2136
        VMCodeMapping.instance().insert_code(vm_code)
2✔
2137

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

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

2152
            if target_address not in self._missing_target:
2✔
2153
                self._missing_target[target_address] = []
2✔
2154
            if vmcode not in self._missing_target[target_address]:
2✔
2155
                self._missing_target[target_address].append(vmcode)
2✔
2156

2157
    def _remove_missing_target(self, vmcode: VMCode):
2✔
2158
        """
2159
        Removes a instruction from the missing target list
2160

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

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

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

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

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

2204
    def _update_codes_without_target_to_next(self, address: int = None):
2✔
2205
        if address is None:
2✔
UNCOV
2206
            address = self.bytecode_size
×
2207

2208
        instance = VMCodeMapping.instance()
2✔
2209
        vm_code = instance.get_code(address)
2✔
2210
        if vm_code is None:
2✔
UNCOV
2211
            return
×
2212

2213
        next_address = instance.get_end_address(vm_code)
2✔
2214
        if address in self._missing_target:
2✔
2215
            targets = self._missing_target.pop(address)
×
2216

2217
            if next_address in self._missing_target:
×
UNCOV
2218
                self._missing_target[next_address].extend(targets)
×
2219
            else:
UNCOV
2220
                self._missing_target[next_address] = targets
×
2221

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

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

2239
                    vmcodes.remove(code)
2✔
2240
            else:
UNCOV
2241
                for code in vmcodes.copy():
×
UNCOV
2242
                    code.set_target(VMCodeMapping.instance().get_code(target))
×
UNCOV
2243
                    vmcodes.remove(code)
×
2244

2245
            if len(vmcodes) == 0:
2✔
2246
                self._missing_target.pop(target)
2✔
2247

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

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

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

2265
    def _update_jump(self, jump_address: int, updated_jump_to: int):
2✔
2266
        """
2267
        Updates the data of a jump code in the bytecode
2268

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

2284
    def change_jump(self, jump_address: int, new_jump_opcode: Opcode):
2✔
2285
        """
2286
        Changes the type of jump code in the bytecode
2287

2288
        """
2289
        if not OpcodeHelper.is_jump(new_jump_opcode):
2✔
UNCOV
2290
            return
×
2291

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

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

2308
                while len(previous_stack) > target_stack_size:
2✔
2309
                    previous_stack.pop(-1)  # consume last stack item as jump condition
2✔
2310

2311
            vmcode.set_opcode(OpcodeInfo.get_info(new_jump_opcode))
2✔
2312

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

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

2320
    def duplicate_stack_top_item(self):
2✔
2321
        self.duplicate_stack_item(1)
2✔
2322

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

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

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

2347
            self._stack_append(stacked_value)
2✔
2348

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

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

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

2372
    def remove_stack_top_item(self):
2✔
2373
        self.remove_stack_item(1)
2✔
2374

2375
    def remove_stack_item(self, pos: int = 0):
2✔
2376
        """
2377
        Removes the item n from the stack
2378

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

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

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

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

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

2422
            self.__insert1(OpcodeInfo.UNPACK)  # unpack for array concatenation
2✔
2423
            value = self._stack_pop()
2✔
2424
            self._stack_append(Type.int)
2✔
2425
            self.remove_stack_top_item()
2✔
2426

2427
            # copy the array that stores the class variables from that class
2428
            self.convert_user_class(class_type, class_type.identifier)
2✔
2429

2430
            self.__insert1(OpcodeInfo.UNPACK)  # unpack for array concatenation
2✔
2431
            self._stack_append(value)
2✔
2432
            self.convert_literal(no_instance_variables)
2✔
2433
            self.convert_operation(BinaryOp.Add)
2✔
2434

2435
            self.__insert1(OpcodeInfo.PACK)  # packs everything together
2✔
2436
            self._stack_pop()
2✔
2437

2438
    def generate_implicit_init_user_class(self, init_method: Method):
2✔
2439
        if not init_method.is_called:
2✔
2440
            return
2✔
2441

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

2448
            for arg_id, arg_var in reversed(list(init_method.args.items())):
2✔
2449
                self.convert_load_variable(arg_id, arg_var)
2✔
2450

2451
            args_to_call = num_args - 1 if num_args > 0 else num_args
2✔
2452
            self.convert_method_call(base_constructor, args_to_call)
2✔
2453
            self.remove_stack_top_item()
2✔
2454

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

© 2026 Coveralls, Inc