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

CityOfZion / neo3-boa / 901be479-27a3-4d37-a8e9-d5889c82bd13

16 Jan 2024 04:06PM UTC coverage: 92.101% (+0.1%) from 91.967%
901be479-27a3-4d37-a8e9-d5889c82bd13

push

circleci

Mirella de Medeiros
CU-86a1n2upk - Notify is not being registered on events when using `notify` as a exported member of another import

20767 of 22548 relevant lines covered (92.1%)

1.84 hits per line

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

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

5

6
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
2✔
7

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

52

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

124
                visitor.global_stmts = static_stmts
2✔
125

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

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

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

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

146
        except CompilerError:
×
147
            pass
×
148

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

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

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

171
        return imports
2✔
172

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

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

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

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

190
        self._scope_stack: List[SymbolScope] = []
2✔
191
        self._global_scope = SymbolScope()
2✔
192

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

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

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

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

207
        self._static_vars: Optional[list] = None
2✔
208
        self._global_vars: Optional[list] = None
2✔
209

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

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

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

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

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

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

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

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

244
    @property
2✔
245
    def last_code(self) -> Optional[VMCode]:
2✔
246
        """
247
        Gets the last code in the bytecode
248

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

420
                            vars_map[index] = new_value
2✔
421

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

430
                result = reordered_ids + additional_items
2✔
431

432
        return result
2✔
433

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

438
    # region Optimization properties
439

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

443
    # endregion
444

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

547
        return num_static_fields > 0
2✔
548

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

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

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

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

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

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

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

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

593
            self.insert_return()
2✔
594

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

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

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

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

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

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

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

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

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

640
        return start_address
2✔
641

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

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

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

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

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

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

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

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

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

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

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

704
        return test_address
2✔
705

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

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

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

725
        self._current_loop.pop()
2✔
726

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

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

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

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

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

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

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

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

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

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

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

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

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

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

804
        return self.last_code_start_address
2✔
805

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

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

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

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

826
        return self.last_code_start_address
2✔
827

828
    def convert_try_except(self, exception_id: Optional[str]) -> int:
2✔
829
        """
830
        Converts the end of the try statement
831

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

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

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

844
        return last_try_code
2✔
845

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

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

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

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

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

871
        return self.last_code_start_address
2✔
872

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1405
                self.convert_get_array_slice(array)
2✔
1406

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1749
        return index, local, is_arg
2✔
1750

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1940
    def convert_user_class(self, class_type: UserClass, symbol_id: str) -> Optional[int]:
2✔
1941
        """
1942
        Converts an class symbol
1943

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

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

1955
        return start_address
2✔
1956

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

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

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

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

1977
            return index
2✔
1978

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2343
            self._stack_append(stacked_value)
2✔
2344

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2451
        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