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

CityOfZion / neo3-boa / 7f1f506c-c93c-47c9-a344-3722b133611e

16 Oct 2023 07:34PM UTC coverage: 91.908% (+0.3%) from 91.625%
7f1f506c-c93c-47c9-a344-3722b133611e

push

circleci

Mirella de Medeiros
squash missing cherry-picks from development

39 of 39 new or added lines in 8 files covered. (100.0%)

20000 of 21761 relevant lines covered (91.91%)

0.92 hits per line

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

95.06
/boa3/internal/compiler/codegenerator/codegenerator.py
1
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
1✔
2

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

44

45
class CodeGenerator:
1✔
46
    """
47
    This class is responsible for generating the Neo VM bytecode
48

49
    :ivar symbol_table: a dictionary that maps the global symbols.
50
    """
51

52
    @staticmethod
1✔
53
    def generate_code(analyser: Analyser) -> CompilerOutput:
1✔
54
        """
55
        Generates the Neo VM bytecode using of the analysed Python code
56

57
        :param analyser: semantic analyser of the Python code
58
        :return: the Neo VM bytecode
59
        """
60
        VMCodeMapping.reset()
1✔
61
        analyser.update_symbol_table_with_imports()
1✔
62

63
        all_imports = CodeGenerator._find_all_imports(analyser)
1✔
64
        generator = CodeGenerator(analyser.symbol_table)
1✔
65

66
        from boa3.internal.exception.CompilerError import CompilerError
1✔
67

68
        try:
1✔
69
            import ast
1✔
70
            from boa3.internal.compiler.codegenerator.codegeneratorvisitor import VisitorCodeGenerator
1✔
71

72
            deploy_method = (analyser.symbol_table[constants.DEPLOY_METHOD_ID]
1✔
73
                             if constants.DEPLOY_METHOD_ID in analyser.symbol_table
74
                             else None)
75
            deploy_origin_module = analyser.ast_tree
1✔
76

77
            if hasattr(deploy_method, 'origin') and deploy_method.origin in analyser.ast_tree.body:
1✔
78
                analyser.ast_tree.body.remove(deploy_method.origin)
1✔
79

80
            visitor = VisitorCodeGenerator(generator, analyser.filename, analyser.root)
1✔
81
            visitor._root_module = analyser.ast_tree
1✔
82
            visitor.visit_and_update_analyser(analyser.ast_tree, analyser)
1✔
83

84
            analyser.update_symbol_table(generator.symbol_table)
1✔
85
            generator.symbol_table.clear()
1✔
86
            generator.symbol_table.update(analyser.symbol_table.copy())
1✔
87

88
            for symbol in all_imports.values():
1✔
89
                generator.symbol_table.update(symbol.all_symbols)
1✔
90

91
                if hasattr(deploy_method, 'origin') and deploy_method.origin in symbol.ast.body:
1✔
92
                    symbol.ast.body.remove(deploy_method.origin)
×
93
                    deploy_origin_module = symbol.ast
×
94

95
                visitor.set_filename(symbol.origin)
1✔
96
                visitor.visit_and_update_analyser(symbol.ast, analyser)
1✔
97

98
                analyser.update_symbol_table(symbol.all_symbols)
1✔
99
                generator.symbol_table.clear()
1✔
100
                generator.symbol_table.update(analyser.symbol_table.copy())
1✔
101

102
            if len(generator._globals) > 0:
1✔
103
                from boa3.internal.compiler.codegenerator.initstatementsvisitor import InitStatementsVisitor
1✔
104
                deploy_stmts, static_stmts = InitStatementsVisitor.separate_global_statements(analyser.symbol_table,
1✔
105
                                                                                              visitor.global_stmts)
106

107
                deploy_method = deploy_method if deploy_method is not None else InnerDeployMethod.instance().copy()
1✔
108

109
                if len(deploy_stmts) > 0:
1✔
110
                    if_update_body = ast.parse(f"if not {list(deploy_method.args)[1]}: pass").body[0]
1✔
111
                    if_update_body.body = deploy_stmts
1✔
112
                    if_update_body.test.op = UnaryOp.Not
1✔
113
                    deploy_method.origin.body.insert(0, if_update_body)
1✔
114

115
                visitor.global_stmts = static_stmts
1✔
116

117
            if hasattr(deploy_method, 'origin'):
1✔
118
                deploy_ast = ast.parse("")
1✔
119
                deploy_ast.body = [deploy_method.origin]
1✔
120

121
                generator.symbol_table[constants.DEPLOY_METHOD_ID] = deploy_method
1✔
122
                analyser.symbol_table[constants.DEPLOY_METHOD_ID] = deploy_method
1✔
123
                visitor._tree = deploy_origin_module
1✔
124
                visitor.visit_and_update_analyser(deploy_ast, analyser)
1✔
125

126
                generator.symbol_table.clear()
1✔
127
                generator.symbol_table.update(analyser.symbol_table.copy())
1✔
128

129
            visitor.set_filename(analyser.filename)
1✔
130
            generator.can_init_static_fields = True
1✔
131
            if len(visitor.global_stmts) > 0:
1✔
132
                global_ast = ast.parse("")
1✔
133
                global_ast.body = visitor.global_stmts
1✔
134
                visitor.visit_and_update_analyser(global_ast, analyser)
1✔
135
                generator.initialized_static_fields = True
1✔
136

137
        except CompilerError:
×
138
            pass
×
139

140
        analyser.update_symbol_table(generator.symbol_table)
1✔
141
        compilation_result = generator.output
1✔
142
        return compilation_result
1✔
143

144
    @staticmethod
1✔
145
    def _find_all_imports(analyser: Analyser) -> Dict[str, Import]:
1✔
146
        imports = {}
1✔
147
        for key, symbol in analyser.symbol_table.copy().items():
1✔
148
            if isinstance(symbol, Import):
1✔
149
                importing = symbol
1✔
150
            elif isinstance(symbol, Package) and isinstance(symbol.origin, Import):
1✔
151
                importing = symbol.origin
1✔
152
                analyser.symbol_table[symbol.origin.origin] = symbol.origin
1✔
153
            else:
154
                importing = None
1✔
155

156
            if importing is not None and importing.origin not in imports:
1✔
157
                imports[importing.origin] = importing
1✔
158

159
        return imports
1✔
160

161
    def __init__(self, symbol_table: Dict[str, ISymbol]):
1✔
162
        self.symbol_table: Dict[str, ISymbol] = symbol_table.copy()
1✔
163
        self.additional_symbols: Optional[Dict[str, ISymbol]] = None
1✔
164

165
        self._current_method: Method = None
1✔
166
        self._current_class: Method = None
1✔
167

168
        self._missing_target: Dict[int, List[VMCode]] = {}  # maps targets with address not included yet
1✔
169
        self._can_append_target: bool = True
1✔
170

171
        self._scope_stack: List[SymbolScope] = []
1✔
172
        self._global_scope = SymbolScope()
1✔
173

174
        self._current_loop: List[int] = []  # a stack with the converting loops' start addresses
1✔
175
        self._current_for: List[int] = []
1✔
176
        self._jumps_to_loop_condition: Dict[int, List[int]] = {}
1✔
177

178
        self._jumps_to_loop_break: Dict[int, List[int]] = {}
1✔
179
        # the indexes of boolean insertion values indicating if the jmp is from a break
180
        self._inserted_loop_breaks: Dict[int, List[int]] = {}
1✔
181

182
        self._opcodes_to_remove: List[int] = []
1✔
183
        self._stack_states: StackMemento = StackMemento()  # simulates neo execution stack
1✔
184

185
        self.can_init_static_fields: bool = False
1✔
186
        self.initialized_static_fields: bool = False
1✔
187

188
        self._static_vars: Optional[list] = None
1✔
189
        self._global_vars: Optional[list] = None
1✔
190

191
    @property
1✔
192
    def bytecode(self) -> bytes:
1✔
193
        """
194
        Gets the bytecode of the translated code
195

196
        :return: the generated bytecode
197
        """
198
        output = self.output
1✔
199
        return output.bytecode
1✔
200

201
    @property
1✔
202
    def output(self) -> CompilerOutput:
1✔
203
        """
204
        Gets the bytecode of the translated code
205

206
        :return: the generated bytecode
207
        """
208
        opcodes = VMCodeMapping.instance().get_opcodes(self._opcodes_to_remove)
1✔
209
        self.set_code_targets()
1✔
210
        VMCodeMapping.instance().remove_opcodes_by_code(opcodes)
1✔
211
        self._opcodes_to_remove.clear()
1✔
212
        return VMCodeMapping.instance().result()
1✔
213

214
    @property
1✔
215
    def last_code(self) -> Optional[VMCode]:
1✔
216
        """
217
        Gets the last code in the bytecode
218

219
        :return: the last code. If the bytecode is empty, returns None
220
        :rtype: VMCode or None
221
        """
222
        if len(VMCodeMapping.instance().codes) > 0:
1✔
223
            return VMCodeMapping.instance().codes[-1]
1✔
224
        else:
225
            return None
1✔
226

227
    @property
1✔
228
    def _stack(self) -> NeoStack:
1✔
229
        return self._stack_states.current_stack
1✔
230

231
    @property
1✔
232
    def stack_size(self) -> int:
1✔
233
        """
234
        Gets the size of the stack
235

236
        :return: the size of the stack of converted values
237
        """
238
        return len(self._stack)
1✔
239

240
    def _stack_append(self, value_type: IType):
1✔
241
        self._stack_states.append(value_type, self.last_code)
1✔
242

243
    def _stack_pop(self, index: int = -1) -> IType:
1✔
244
        return self._stack_states.pop(self.last_code, index)
1✔
245

246
    @property
1✔
247
    def last_code_start_address(self) -> int:
1✔
248
        """
249
        Gets the first address from last code in the bytecode
250

251
        :return: the last code's first address
252
        """
253
        instance = VMCodeMapping.instance()
1✔
254
        if len(instance.codes) > 0:
1✔
255
            return instance.get_start_address(instance.codes[-1])
1✔
256
        else:
257
            return 0
1✔
258

259
    @property
1✔
260
    def bytecode_size(self) -> int:
1✔
261
        """
262
        Gets the current bytecode size
263

264
        :return: the current bytecode size
265
        """
266
        return VMCodeMapping.instance().bytecode_size
1✔
267

268
    @property
1✔
269
    def _args(self) -> List[str]:
1✔
270
        """
271
        Gets a list with the arguments names of the current method
272

273
        :return: A list with the arguments names
274
        """
275
        return [] if self._current_method is None else list(self._current_method.args.keys())
1✔
276

277
    @property
1✔
278
    def _locals(self) -> List[str]:
1✔
279
        """
280
        Gets a list with the variables names in the scope of the current method
281

282
        :return: A list with the variables names
283
        """
284
        return [] if self._current_method is None else list(self._current_method.locals.keys())
1✔
285

286
    @property
1✔
287
    def _globals(self) -> List[str]:
1✔
288
        return self._module_variables(True)
1✔
289

290
    @property
1✔
291
    def _statics(self) -> List[str]:
1✔
292
        return self._module_variables(False)
1✔
293

294
    def _module_variables(self, modified_variable: bool) -> List[str]:
1✔
295
        """
296
        Gets a list with the variables name in the global scope
297

298
        :return: A list with the variables names
299
        """
300
        if modified_variable:
1✔
301
            vars_map = self._global_vars
1✔
302
        else:
303
            vars_map = self._static_vars
1✔
304

305
        module_global_variables = []
1✔
306
        module_global_ids = []
1✔
307
        result_global_vars = []
1✔
308
        for var_id, var in self.symbol_table.items():
1✔
309
            if isinstance(var, Variable) and var.is_reassigned == modified_variable and var not in result_global_vars:
1✔
310
                module_global_variables.append((var_id, var))
1✔
311
                module_global_ids.append(var_id)
1✔
312
                result_global_vars.append(var)
1✔
313

314
        class_with_class_variables = []
1✔
315
        class_with_variables_ids = []
1✔
316
        for class_id, class_symbol in self.symbol_table.items():
1✔
317
            if isinstance(class_symbol, UserClass) and len(class_symbol.class_variables) > 0:
1✔
318
                class_with_class_variables.append((class_id, class_symbol))
1✔
319
                class_with_variables_ids.append(class_id)
1✔
320

321
        if not self.can_init_static_fields:
1✔
322
            for imported in self.symbol_table.values():
1✔
323
                if isinstance(imported, Import):
1✔
324
                    # tried to use set and just update, but we need the variables to be ordered
325
                    for var_id, var in imported.variables.items():
1✔
326
                        if (isinstance(var, Variable)
1✔
327
                                and var.is_reassigned == modified_variable
328
                                and var_id not in module_global_ids
329
                                and var not in result_global_vars):
330
                            module_global_variables.append((var_id, var))
1✔
331
                            module_global_ids.append(var_id)
1✔
332
                            result_global_vars.append(var)
1✔
333

334
                    # TODO: include user class from imported symbols as well
335

336
        if modified_variable:
1✔
337
            result_map = module_global_variables
1✔
338
            result = module_global_ids
1✔
339
        else:
340
            result_map = module_global_variables + class_with_class_variables
1✔
341
            result_global_vars = result_global_vars + [classes for (class_id, classes) in class_with_class_variables]
1✔
342
            result = module_global_ids + class_with_variables_ids
1✔
343

344
        if vars_map != result_map:
1✔
345
            if vars_map is None:
1✔
346
                # save to keep the same order in future accesses
347
                if modified_variable:
1✔
348
                    self._global_vars = result_map
1✔
349
                else:
350
                    self._static_vars = result_map
1✔
351

352
            else:
353
                # reorder to keep the same order as the first access
354
                if len(result_map) > len(vars_map):
1✔
355
                    additional_items = []
1✔
356
                    vars_list = [var for var_id, var in vars_map]
1✔
357
                    for index, var in enumerate(result_global_vars):
1✔
358
                        if var in vars_list:
1✔
359
                            var_index = vars_list.index(var)
1✔
360
                            vars_map[var_index] = result_map[index]
1✔
361
                        else:
362
                            additional_items.append(result_map[index])
1✔
363
                    vars_map.extend(additional_items)
1✔
364

365
                    pre_reordered_ids = [var_id for (var_id, var) in vars_map]
1✔
366
                else:
367
                    original_ids = []
1✔
368
                    for value in result:
1✔
369
                        split = value.split(constants.VARIABLE_NAME_SEPARATOR)
1✔
370
                        if len(split) > 1:
1✔
371
                            new_index = split[-1]
1✔
372
                        else:
373
                            new_index = value
1✔
374
                        original_ids.append(new_index)
1✔
375

376
                    pre_reordered_ids = [var_id for (var_id, var) in vars_map]
1✔
377
                    for index, (value, var) in enumerate(vars_map):
1✔
378
                        if value not in result:
1✔
379
                            if var in result_global_vars:
1✔
380
                                var_index = result_global_vars.index(var)
1✔
381
                                new_value = result_map[var_index]
1✔
382
                            else:
383
                                var_index = original_ids.index(value)
×
384
                                new_value = result_map[var_index]
×
385

386
                            vars_map[index] = new_value
1✔
387

388
                # add new symbols at the end always
389
                reordered_ids = [var_id for (var_id, var) in vars_map]
1✔
390
                additional_items = []
1✔
391
                for index, var_id in enumerate(result):
1✔
392
                    if var_id not in reordered_ids and var_id not in pre_reordered_ids:
1✔
393
                        additional_items.append(var_id)
×
394
                        vars_map.append(result_map[index])
×
395

396
                result = reordered_ids + additional_items
1✔
397

398
        return result
1✔
399

400
    @property
1✔
401
    def _current_scope(self) -> SymbolScope:
1✔
402
        return self._scope_stack[-1] if len(self._scope_stack) > 0 else self._global_scope
1✔
403

404
    def is_none_inserted(self) -> bool:
1✔
405
        """
406
        Checks whether the last insertion is null
407

408
        :return: whether the last value is null
409
        """
410
        return (self.last_code.opcode is Opcode.PUSHNULL or
1✔
411
                (len(self._stack) > 0 and self._stack[-1] is Type.none))
412

413
    def get_symbol(self, identifier: str, scope: Optional[ISymbol] = None, is_internal: bool = False) -> Tuple[str, ISymbol]:
1✔
414
        """
415
        Gets a symbol in the symbol table by its id
416

417
        :param identifier: id of the symbol
418
        :return: the symbol if exists. Symbol None otherwise
419
        """
420
        cur_symbol_table = self.symbol_table.copy()
1✔
421
        if isinstance(self.additional_symbols, dict):
1✔
422
            cur_symbol_table.update(self.additional_symbols)
×
423

424
        found_id = None
1✔
425
        found_symbol = None
1✔
426
        if len(self._scope_stack) > 0:
1✔
427
            for symbol_scope in self._scope_stack:
1✔
428
                if identifier in symbol_scope:
1✔
429
                    found_id, found_symbol = identifier, symbol_scope[identifier]
1✔
430
                    break
1✔
431

432
        if found_id is None:
1✔
433
            if scope is not None and hasattr(scope, 'symbols') and isinstance(scope.symbols, dict):
1✔
434
                if identifier in scope.symbols and isinstance(scope.symbols[identifier], ISymbol):
×
435
                    found_id, found_symbol = identifier, scope.symbols[identifier]
×
436
            else:
437
                if self._current_method is not None and identifier in self._current_method.symbols:
1✔
438
                    found_id, found_symbol = identifier, self._current_method.symbols[identifier]
1✔
439
                elif identifier in cur_symbol_table:
1✔
440
                    found_id, found_symbol = identifier, cur_symbol_table[identifier]
1✔
441
                else:
442
                    # the symbol may be a built-in. If not, returns None
443
                    symbol = Builtin.get_symbol(identifier)
1✔
444
                    if symbol is None:
1✔
445
                        symbol = Interop.get_symbol(identifier)
1✔
446

447
                    if symbol is not None:
1✔
448
                        found_id, found_symbol = identifier, symbol
1✔
449

450
                    elif not isinstance(identifier, str):
1✔
451
                        found_id, found_symbol = identifier, symbol
×
452

453
                    else:
454
                        split = identifier.split(constants.ATTRIBUTE_NAME_SEPARATOR)
1✔
455
                        if len(split) > 1:
1✔
456
                            attribute, symbol_id = constants.ATTRIBUTE_NAME_SEPARATOR.join(split[:-1]), split[-1]
1✔
457
                            another_attr_id, attr = self.get_symbol(attribute, is_internal=is_internal)
1✔
458
                            if hasattr(attr, 'symbols') and symbol_id in attr.symbols:
1✔
459
                                found_id, found_symbol = symbol_id, attr.symbols[symbol_id]
1✔
460
                            elif isinstance(attr, Package) and symbol_id in attr.inner_packages:
1✔
461
                                found_id, found_symbol = symbol_id, attr.inner_packages[symbol_id]
1✔
462

463
                        if found_id is None and is_internal:
1✔
464
                            from boa3.internal.model import imports
×
465
                            found_symbol = imports.builtin.get_internal_symbol(identifier)
×
466
                            if isinstance(found_symbol, ISymbol):
×
467
                                found_id = identifier
×
468

469
        if found_id is not None:
1✔
470
            if isinstance(found_symbol, Variable) and not found_symbol.is_reassigned and found_id not in self._statics:
1✔
471
                # verifies if it's a static variable with a unique name
472
                for static_id, static_var in self._static_vars:
1✔
473
                    if found_symbol == static_var:
1✔
474
                        found_id = static_id
×
475
                        break
×
476

477
            return found_id, found_symbol
1✔
478
        return identifier, Type.none
1✔
479

480
    def initialize_static_fields(self) -> bool:
1✔
481
        """
482
        Converts the signature of the method
483

484
        :return: whether there are static fields to be initialized
485
        """
486
        if not self.can_init_static_fields:
1✔
487
            return False
1✔
488
        if self.initialized_static_fields:
1✔
489
            return False
×
490

491
        num_static_fields = len(self._statics)
1✔
492
        if num_static_fields > 0:
1✔
493
            init_data = bytearray([num_static_fields])
1✔
494
            self.__insert1(OpcodeInfo.INITSSLOT, init_data)
1✔
495

496
            if constants.INITIALIZE_METHOD_ID in self.symbol_table:
1✔
497
                from boa3.internal.helpers import get_auxiliary_name
×
498
                method = self.symbol_table.pop(constants.INITIALIZE_METHOD_ID)
×
499
                new_id = get_auxiliary_name(constants.INITIALIZE_METHOD_ID, method)
×
500
                self.symbol_table[new_id] = method
×
501

502
            init_method = Method(is_public=True)
1✔
503
            init_method.init_bytecode = self.last_code
1✔
504
            self.symbol_table[constants.INITIALIZE_METHOD_ID] = init_method
1✔
505

506
        return num_static_fields > 0
1✔
507

508
    def end_initialize(self):
1✔
509
        """
510
        Converts the signature of the method
511
        """
512
        self.insert_return()
1✔
513
        self.initialized_static_fields = True
1✔
514

515
        if constants.INITIALIZE_METHOD_ID in self.symbol_table:
1✔
516
            init_method = self.symbol_table[constants.INITIALIZE_METHOD_ID]
1✔
517
            init_method.end_bytecode = self.last_code
1✔
518

519
    def convert_begin_method(self, method: Method):
1✔
520
        """
521
        Converts the signature of the method
522

523
        :param method: method that is being converted
524
        """
525
        new_variable_scope = self._scope_stack[-1].copy() if len(self._scope_stack) > 0 else SymbolScope()
1✔
526
        self._scope_stack.append(new_variable_scope)
1✔
527

528
        num_args: int = len(method.args)
1✔
529
        num_vars: int = len(method.locals)
1✔
530

531
        method.init_address = VMCodeMapping.instance().bytecode_size
1✔
532
        if num_args > 0 or num_vars > 0:
1✔
533
            init_data = bytearray([num_vars, num_args])
1✔
534
            self.__insert1(OpcodeInfo.INITSLOT, init_data)
1✔
535
            method.init_bytecode = self.last_code
1✔
536
        self._current_method = method
1✔
537

538
    def convert_end_method(self, method_id: Optional[str] = None):
1✔
539
        """
540
        Converts the end of the method
541
        """
542
        if (self._current_method.init_bytecode is None
1✔
543
                and self._current_method.init_address in VMCodeMapping.instance().code_map):
544
            self._current_method.init_bytecode = VMCodeMapping.instance().code_map[self._current_method.init_address]
1✔
545

546
        if self.last_code.opcode is not Opcode.RET or self._check_codes_with_target():
1✔
547
            if self._current_method.is_init:
1✔
548
                # return the built object if it's a constructor
549
                self_id, self_value = list(self._current_method.args.items())[0]
1✔
550
                self.convert_load_variable(self_id, self_value)
1✔
551

552
            self.insert_return()
1✔
553

554
        self._current_method.end_bytecode = self.last_code
1✔
555
        self._current_method = None
1✔
556
        self._stack.clear()
1✔
557

558
        function_variable_scope = self._scope_stack.pop()
1✔
559

560
    def insert_return(self):
1✔
561
        """
562
        Insert the return statement
563
        """
564
        self.__insert1(OpcodeInfo.RET)
1✔
565

566
    def insert_not(self):
1✔
567
        """
568
        Insert a `not` to change the value of a bool
569
        """
570
        self.__insert1(OpcodeInfo.NOT)
×
571

572
    def insert_nop(self):
1✔
573
        """
574
        Insert a NOP opcode
575
        """
576
        self.__insert1(OpcodeInfo.NOP)
1✔
577

578
    def insert_sys_call(self, sys_call_id: bytes):
1✔
579
        """
580
        Insert a SYSCALL opcode call
581
        """
582
        self.__insert1(OpcodeInfo.SYSCALL, sys_call_id)
1✔
583

584
    def convert_begin_while(self, is_for: bool = False) -> int:
1✔
585
        """
586
        Converts the beginning of the while statement
587

588
        :param is_for: whether the loop is a for loop or not
589
        :return: the address of the while first opcode
590
        """
591
        # it will be updated when the while ends
592
        self._insert_jump(OpcodeInfo.JMP)
1✔
593

594
        start_address = self.last_code_start_address
1✔
595
        self._current_loop.append(start_address)
1✔
596
        if is_for:
1✔
597
            self._current_for.append(start_address)
1✔
598

599
        return start_address
1✔
600

601
    def convert_end_while(self, start_address: int, test_address: int, *, is_internal: bool = False) -> int:
1✔
602
        """
603
        Converts the end of the while statement
604

605
        :param start_address: the address of the while first opcode
606
        :param test_address: the address of the while test fist opcode
607
        :param is_internal: whether it was called when generating other implemented symbols
608
        """
609
        return self.convert_end_loop(start_address, test_address, False, is_internal=is_internal)
1✔
610

611
    def convert_begin_for(self) -> int:
1✔
612
        """
613
        Converts the beginning of the for statement
614

615
        :return: the address of the for first opcode
616
        """
617
        is_neo_iterator = len(self._stack) > 0 and Interop.Iterator.is_type_of(self._stack[-1])
1✔
618
        if is_neo_iterator:
1✔
619
            self.duplicate_stack_top_item()
1✔
620
        else:
621
            self.convert_literal(0)
1✔
622

623
        address = self.convert_begin_while(True)
1✔
624

625
        if is_neo_iterator:
1✔
626
            self.duplicate_stack_top_item()
1✔
627
            self.convert_builtin_method_call(Interop.IteratorValue)
1✔
628
        else:
629
            self.duplicate_stack_item(2)  # duplicate for sequence
1✔
630
            self.duplicate_stack_item(2)  # duplicate for index
1✔
631
            self.convert_get_item()
1✔
632
        return address
1✔
633

634
    def convert_end_for(self, start_address: int, is_internal: bool = False) -> int:
1✔
635
        """
636
        Converts the end of the for statement
637

638
        :param start_address: the address of the for first opcode
639
        :param is_internal: whether it was called when generating other implemented symbols
640
        :return: the address of the loop condition
641
        """
642
        is_neo_iterator = len(self._stack) > 0 and Interop.Iterator.is_type_of(self._stack[-1])
1✔
643
        if not is_neo_iterator:
1✔
644
            self.__insert1(OpcodeInfo.INC)      # index += 1
1✔
645
            if len(self._stack) < 1 or self._stack[-1] is not Type.int:
1✔
646
                self._stack_append(Type.int)
1✔
647

648
        for_increment = self.last_code_start_address
1✔
649
        test_address = VMCodeMapping.instance().bytecode_size
1✔
650
        self._update_continue_jumps(start_address, for_increment)
1✔
651

652
        if is_neo_iterator:
1✔
653
            self.duplicate_stack_top_item()
1✔
654
            self.convert_builtin_method_call(Interop.IteratorNext)
1✔
655
        else:
656
            self.duplicate_stack_top_item()     # dup index and sequence
1✔
657
            self.duplicate_stack_item(3)
1✔
658
            self.convert_builtin_method_call(Builtin.Len)
1✔
659
            self.convert_operation(BinaryOp.Lt)  # continue loop condition: index < len(sequence)
1✔
660

661
        self.convert_end_loop(start_address, test_address, True, is_internal=is_internal)
1✔
662

663
        return test_address
1✔
664

665
    def convert_end_loop(self, start_address: int, test_address: int, is_for: bool, is_internal: bool = False) -> int:
1✔
666
        """
667
        Converts the end of a loop statement
668

669
        :param start_address: the address of the while first opcode
670
        :param test_address: the address of the while test fist opcode
671
        :param is_for: whether the loop is a for loop or not
672
        :param is_internal: whether it was called when generating other implemented symbols
673
        """
674
        # updates the begin jmp with the target address
675
        self._update_jump(start_address, test_address)
1✔
676
        self._update_continue_jumps(start_address, test_address)
1✔
677

678
        # inserts end jmp
679
        while_begin: VMCode = VMCodeMapping.instance().code_map[start_address]
1✔
680
        while_body: int = VMCodeMapping.instance().get_end_address(while_begin) + 1
1✔
681
        end_jmp_to: int = while_body - VMCodeMapping.instance().bytecode_size
1✔
682
        self._insert_jump(OpcodeInfo.JMPIF, end_jmp_to)
1✔
683

684
        self._current_loop.pop()
1✔
685

686
        is_break_pos = self.bytecode_size
1✔
687
        self.convert_literal(False)  # is not break
1✔
688
        is_break_end = self.last_code_start_address
1✔
689
        self._update_break_jumps(start_address)
1✔
690

691
        if is_for:
1✔
692
            self._current_for.pop()
1✔
693
            reverse_to_drop_pos = self.last_code_start_address
1✔
694
            self.swap_reverse_stack_items(3)
1✔
695
            reverse_to_drop_end = self.last_code_start_address
1✔
696

697
            self.remove_stack_top_item()    # removes index and sequence from stack
1✔
698
            self.remove_stack_top_item()
1✔
699

700
            self._insert_loop_break_addresses(start_address, reverse_to_drop_pos, reverse_to_drop_end, self.bytecode_size)
1✔
701

702
        last_opcode = self.bytecode_size
1✔
703
        self._insert_loop_break_addresses(start_address, is_break_pos, is_break_end, last_opcode)
1✔
704
        self._insert_jump(OpcodeInfo.JMPIF)
1✔
705

706
        if is_internal:
1✔
707
            self.convert_end_loop_else(start_address, self.last_code_start_address)
1✔
708
        return last_opcode
1✔
709

710
    def convert_end_loop_else(self, start_address: int, else_begin: int, has_else: bool = False, is_for: bool = False):
1✔
711
        """
712
        Updates the break loops jumps
713

714
        :param start_address: the address of the loop first opcode
715
        :param else_begin: the address of the else first opcode. Equals to code size if has_else is False
716
        :param has_else: whether this loop has an else branch
717
        :param is_for: whether the loop is a for loop or not
718
        """
719
        if start_address in self._jumps_to_loop_break:
1✔
720
            is_loop_insertions = []
1✔
721
            if start_address in self._inserted_loop_breaks:
1✔
722
                is_loop_insertions = self._inserted_loop_breaks.pop(start_address)
1✔
723
            is_loop_insertions.append(else_begin)
1✔
724

725
            if not has_else:
1✔
726
                self._opcodes_to_remove.extend(is_loop_insertions)
1✔
727
            else:
728
                min_break_addresses = 4 if is_for else 3
1✔
729
                if (start_address in self._jumps_to_loop_break
1✔
730
                        and len(self._jumps_to_loop_break[start_address]) < 2
731
                        and len(is_loop_insertions) < min_break_addresses):
732
                    # if len is less than 2, it means it has no breaks or the only break is else branch begin
733
                    # so it can remove the jump in the beginning of else branch
734
                    self._opcodes_to_remove.extend(is_loop_insertions)
1✔
735
                self._update_jump(else_begin, VMCodeMapping.instance().bytecode_size)
1✔
736

737
    def convert_begin_if(self) -> int:
1✔
738
        """
739
        Converts the beginning of the if statement
740

741
        :return: the address of the if first opcode
742
        """
743
        # it will be updated when the if ends
744
        self._insert_jump(OpcodeInfo.JMPIFNOT)
1✔
745
        return VMCodeMapping.instance().get_start_address(self.last_code)
1✔
746

747
    def convert_begin_else(self, start_address: int, insert_jump: bool = False, is_internal: bool = False) -> int:
1✔
748
        """
749
        Converts the beginning of the if else statement
750

751
        :param start_address: the address of the if first opcode
752
        :param insert_jump: whether it should be included a jump to the end before the else branch
753
        :return: the address of the if else first opcode
754
        """
755
        # it will be updated when the if ends
756
        self._insert_jump(OpcodeInfo.JMP, insert_jump=insert_jump)
1✔
757

758
        # updates the begin jmp with the target address
759
        self._update_jump(start_address, VMCodeMapping.instance().bytecode_size)
1✔
760
        if is_internal:
1✔
761
            self._stack_states.restore_state(start_address + 1)
1✔
762

763
        return self.last_code_start_address
1✔
764

765
    def convert_end_if(self, start_address: int, is_internal: bool = False):
1✔
766
        """
767
        Converts the end of the if statement
768

769
        :param start_address: the address of the if first opcode
770
        """
771
        # updates the begin jmp with the target address
772
        self._update_jump(start_address, VMCodeMapping.instance().bytecode_size)
1✔
773
        if is_internal:
1✔
774
            self._stack_states.restore_state(start_address)
1✔
775

776
    def convert_begin_try(self) -> int:
1✔
777
        """
778
        Converts the beginning of the try statement
779

780
        :return: the address of the try first opcode
781
        """
782
        # it will be updated when the while ends
783
        self.__insert_code(TryCode())
1✔
784

785
        return self.last_code_start_address
1✔
786

787
    def convert_try_except(self, exception_id: Optional[str]) -> int:
1✔
788
        """
789
        Converts the end of the try statement
790

791
        :param exception_id: the name identifier of the exception
792
        :type exception_id: str or None
793

794
        :return: the last address from try body
795
        """
796
        self._insert_jump(OpcodeInfo.JMP)
1✔
797
        last_try_code = self.last_code_start_address
1✔
798

799
        self._stack_append(Type.exception)  # when reaching the except body, an exception was raised
1✔
800
        if exception_id is None:
1✔
801
            self.remove_stack_top_item()
1✔
802

803
        return last_try_code
1✔
804

805
    def convert_end_try(self, start_address: int,
1✔
806
                        end_address: Optional[int] = None,
807
                        else_address: Optional[int] = None) -> int:
808
        """
809
        Converts the end of the try statement
810

811
        :param start_address: the address of the try first opcode
812
        :param end_address: the address of the try last opcode. If it is None, there's no except body.
813
        :param else_address: the address of the try else. If it is None, there's no else body.
814
        :return: the last address of the except body
815
        """
816
        self.__insert1(OpcodeInfo.ENDTRY)
1✔
817
        if end_address is not None:
1✔
818
            vmcode_mapping_instance = VMCodeMapping.instance()
1✔
819

820
            try_vm_code = vmcode_mapping_instance.get_code(start_address)
1✔
821
            try_jump = vmcode_mapping_instance.get_code(end_address)
1✔
822

823
            except_start_address = vmcode_mapping_instance.get_end_address(try_jump) + 1
1✔
824
            except_start_code = vmcode_mapping_instance.get_code(except_start_address)
1✔
825

826
            if isinstance(try_vm_code, TryCode):
1✔
827
                try_vm_code.set_except_code(except_start_code)
1✔
828
            self._update_jump(else_address if else_address is not None else end_address, self.last_code_start_address)
1✔
829

830
        return self.last_code_start_address
1✔
831

832
    def convert_end_try_finally(self, last_address: int, start_address: int, has_try_body: bool = False):
1✔
833
        """
834
        Converts the end of the try finally statement
835

836
        :param last_address: the address of the try except last opcode.
837
        :param start_address: the address of the try first opcode
838
        :param has_try_body: whether this try statement has a finally body.
839
        :return: the last address of the except body
840
        """
841
        if has_try_body:
1✔
842
            self.__insert1(OpcodeInfo.ENDFINALLY)
1✔
843
            vmcode_mapping_instance = VMCodeMapping.instance()
1✔
844

845
            try_vm_code = vmcode_mapping_instance.get_code(start_address)
1✔
846
            try_last_code = vmcode_mapping_instance.get_code(last_address)
1✔
847

848
            finally_start_address = vmcode_mapping_instance.get_end_address(try_last_code) + 1
1✔
849
            finally_start_code = vmcode_mapping_instance.get_code(finally_start_address)
1✔
850

851
            if isinstance(try_vm_code, TryCode):
1✔
852
                try_vm_code.set_finally_code(finally_start_code)
1✔
853
            self._update_jump(vmcode_mapping_instance.bytecode_size, self.last_code_start_address)
1✔
854

855
        self._update_jump(last_address, VMCodeMapping.instance().bytecode_size)
1✔
856

857
    def fix_negative_index(self, value_index: int = None, test_is_negative=True):
1✔
858
        self._can_append_target = not self._can_append_target
1✔
859

860
        value_code = self.last_code_start_address
1✔
861
        size = VMCodeMapping.instance().bytecode_size
1✔
862

863
        if test_is_negative:
1✔
864
            self.duplicate_stack_top_item()
1✔
865
            self.__insert1(OpcodeInfo.SIGN)
1✔
866
            self.convert_literal(-1)
1✔
867

868
            jmp_address = VMCodeMapping.instance().bytecode_size
1✔
869
            self._insert_jump(OpcodeInfo.JMPNE)     # if index < 0
1✔
870

871
        state = self._stack_states.get_state(value_index) if isinstance(value_index, int) else self._stack
1✔
872
        # get position of collection relative to top
873
        index_of_last = -1
1✔
874
        for index, value in reversed(list(enumerate(state))):
1✔
875
            if isinstance(value, ICollectionType):
1✔
876
                index_of_last = index
1✔
877
                break
1✔
878

879
        if index_of_last >= 0:
1✔
880
            pos_from_top = len(state) - index_of_last
1✔
881
        else:
882
            pos_from_top = 2
1✔
883

884
        self.duplicate_stack_item(pos_from_top)     # index += len(array)
1✔
885
        self.convert_builtin_method_call(Builtin.Len)
1✔
886
        self.convert_operation(BinaryOp.Add)
1✔
887

888
        if test_is_negative:
1✔
889
            if not isinstance(value_index, int):
1✔
890
                value_index = VMCodeMapping.instance().bytecode_size
1✔
891
            jmp_target = value_index if value_index < size else VMCodeMapping.instance().bytecode_size
1✔
892
            self._update_jump(jmp_address, jmp_target)
1✔
893

894
            VMCodeMapping.instance().move_to_end(value_index, value_code)
1✔
895

896
        self._can_append_target = not self._can_append_target
1✔
897

898
    def fix_index_out_of_range(self, has_another_index_in_stack: bool):
1✔
899
        """
900
        Will fix a negative index to 0 or an index greater than the sequence length to the length.
901

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

904
        :param has_another_index_in_stack: whether the stack is [..., Sequence, index, index] or [..., Sequence, index].
905
        """
906
        # if index is still negative, then it should be 0
907
        self.duplicate_stack_item(2 if has_another_index_in_stack else 1)
1✔
908
        self.__insert1(OpcodeInfo.SIGN)
1✔
909
        self.convert_literal(-1)
1✔
910
        jmp_address = VMCodeMapping.instance().bytecode_size
1✔
911
        self._insert_jump(OpcodeInfo.JMPNE)  # if index < 0, then index = 0
1✔
912

913
        if has_another_index_in_stack:
1✔
914
            self.swap_reverse_stack_items(2)
1✔
915
        self.remove_stack_top_item()
1✔
916
        self.convert_literal(0)
1✔
917
        if has_another_index_in_stack:
1✔
918
            self.swap_reverse_stack_items(2)
1✔
919
        jmp_target = VMCodeMapping.instance().bytecode_size
1✔
920
        self._update_jump(jmp_address, jmp_target)
1✔
921

922
        # index can not be greater than len(string)
923
        self.duplicate_stack_item(3 if has_another_index_in_stack else 2)
1✔
924
        self.convert_builtin_method_call(Builtin.Len)
1✔
925
        self.__insert1(
1✔
926
            OpcodeInfo.MIN)  # the builtin MinMethod accepts more than 2 arguments, that's why this Opcode is being directly inserted
927
        self._stack_pop()
1✔
928

929
    def fix_index_negative_stride(self):
1✔
930
        """
931
        If stride is negative, then the array was reversed, thus, lower and upper should be changed
932
        accordingly.
933
        The Opcodes below will only fix 1 index.
934
        array[lower:upper:-1] == reversed_array[len(array)-lower-1:len(array)-upper-1]
935
        If lower or upper was None, then it's not necessary to change its values.
936
        """
937
        # top array should be: len(array), index
938
        self.convert_builtin_method_call(Builtin.Len)
1✔
939
        self.swap_reverse_stack_items(2)
1✔
940
        self.convert_operation(BinaryOp.Sub)
1✔
941
        self.__insert1(OpcodeInfo.DEC)
1✔
942

943
    def convert_loop_continue(self):
1✔
944
        loop_start = self._current_loop[-1]
1✔
945
        self._insert_jump(OpcodeInfo.JMP)
1✔
946
        continue_address = self.last_code_start_address
1✔
947

948
        if loop_start not in self._jumps_to_loop_condition:
1✔
949
            self._jumps_to_loop_condition[loop_start] = [continue_address]
1✔
950
        else:
951
            self._jumps_to_loop_condition[loop_start].append(continue_address)
×
952

953
    def _update_continue_jumps(self, loop_start_address, loop_test_address):
1✔
954
        if loop_start_address in self._jumps_to_loop_condition:
1✔
955
            jump_addresses = self._jumps_to_loop_condition.pop(loop_start_address)
1✔
956
            for address in jump_addresses:
1✔
957
                self._update_jump(address, loop_test_address)
1✔
958

959
    def convert_loop_break(self):
1✔
960
        loop_start = self._current_loop[-1]
1✔
961
        is_break_pos = self.bytecode_size
1✔
962
        self.convert_literal(True)  # is break
1✔
963
        self._stack_pop()
1✔
964
        is_break_end = self.last_code_start_address
1✔
965
        self._insert_jump(OpcodeInfo.JMP)
1✔
966
        break_address = self.last_code_start_address
1✔
967

968
        self._insert_loop_break_addresses(loop_start, is_break_pos, is_break_end, break_address)
1✔
969

970
    def _insert_loop_break_addresses(self, loop_start: int, is_break_start: int, is_break_end: int, break_address: int):
1✔
971
        if loop_start not in self._jumps_to_loop_break:
1✔
972
            self._jumps_to_loop_break[loop_start] = [break_address]
1✔
973
        elif break_address not in self._jumps_to_loop_break[loop_start]:
1✔
974
            self._jumps_to_loop_break[loop_start].append(break_address)
1✔
975

976
        is_break_instructions = VMCodeMapping.instance().get_addresses(is_break_start, is_break_end)
1✔
977

978
        if loop_start not in self._inserted_loop_breaks:
1✔
979
            self._inserted_loop_breaks[loop_start] = is_break_instructions
1✔
980
        else:
981
            loop_breaks_list = self._inserted_loop_breaks[loop_start]
1✔
982
            for address in is_break_instructions:
1✔
983
                # don't include duplicated addresses
984
                if address not in loop_breaks_list:
1✔
985
                    loop_breaks_list.append(address)
1✔
986

987
    def _update_break_jumps(self, loop_start_address) -> int:
1✔
988
        jump_target = VMCodeMapping.instance().bytecode_size
1✔
989

990
        if loop_start_address in self._jumps_to_loop_break:
1✔
991
            jump_addresses = self._jumps_to_loop_break.pop(loop_start_address)
1✔
992
            for address in jump_addresses:
1✔
993
                self._update_jump(address, jump_target)
1✔
994

995
    def convert_literal(self, value: Any) -> int:
1✔
996
        """
997
        Converts a literal value
998

999
        :param value: the value to be converted
1000
        :return: the converted value's start address in the bytecode
1001
        """
1002
        start_address = VMCodeMapping.instance().bytecode_size
1✔
1003
        if isinstance(value, bool):
1✔
1004
            self.convert_bool_literal(value)
1✔
1005
        elif isinstance(value, int):
1✔
1006
            self.convert_integer_literal(value)
1✔
1007
        elif isinstance(value, str):
1✔
1008
            self.convert_string_literal(value)
1✔
1009
        elif value is None:
1✔
1010
            self.insert_none()
1✔
1011
        elif isinstance(value, (bytes, bytearray)):
1✔
1012
            self.convert_byte_array(value)
1✔
1013
        elif isinstance(value, Sequence):
1✔
1014
            self.convert_sequence_literal(value)
1✔
1015
        elif isinstance(value, dict):
1✔
1016
            self.convert_dict_literal(value)
1✔
1017
        else:
1018
            # it's not a type that is supported by neo-boa
1019
            raise NotImplementedError
×
1020
        return start_address
1✔
1021

1022
    def convert_integer_literal(self, value: int):
1✔
1023
        """
1024
        Converts an integer literal value
1025

1026
        :param value: the value to be converted
1027
        """
1028
        opcode = OpcodeHelper.get_literal_push(value)
1✔
1029
        if opcode is not None:
1✔
1030
            op_info: OpcodeInformation = OpcodeInfo.get_info(opcode)
1✔
1031
            self.__insert1(op_info)
1✔
1032
            self._stack_append(Type.int)
1✔
1033
        else:
1034
            opcode = OpcodeHelper.get_literal_push(-value)
1✔
1035
            if opcode is not None:
1✔
1036
                op_info: OpcodeInformation = OpcodeInfo.get_info(opcode)
1✔
1037
                self.__insert1(op_info)
1✔
1038
                self._stack_append(Type.int)
1✔
1039
                self.convert_operation(UnaryOp.Negative)
1✔
1040
            else:
1041
                opcode, data = OpcodeHelper.get_push_and_data(value)
1✔
1042
                op_info: OpcodeInformation = OpcodeInfo.get_info(opcode)
1✔
1043
                self.__insert1(op_info, data)
1✔
1044
                self._stack_append(Type.int)
1✔
1045

1046
    def convert_string_literal(self, value: str):
1✔
1047
        """
1048
        Converts an string literal value
1049

1050
        :param value: the value to be converted
1051
        """
1052
        array = bytes(value, constants.ENCODING)
1✔
1053
        self.insert_push_data(array)
1✔
1054
        self.convert_cast(Type.str)
1✔
1055

1056
    def convert_bool_literal(self, value: bool):
1✔
1057
        """
1058
        Converts an boolean literal value
1059

1060
        :param value: the value to be converted
1061
        """
1062
        if value:
1✔
1063
            self.__insert1(OpcodeInfo.PUSHT)
1✔
1064
        else:
1065
            self.__insert1(OpcodeInfo.PUSHF)
1✔
1066
        self._stack_append(Type.bool)
1✔
1067

1068
    def convert_sequence_literal(self, sequence: Sequence):
1✔
1069
        """
1070
        Converts a sequence value
1071

1072
        :param sequence: the value to be converted
1073
        """
1074
        if isinstance(sequence, tuple):
1✔
1075
            value_type = Type.tuple.build(sequence)
1✔
1076
        else:
1077
            value_type = Type.list.build(list(sequence))
1✔
1078

1079
        for inner_value in reversed(sequence):
1✔
1080
            self.convert_literal(inner_value)
1✔
1081

1082
        self.convert_new_array(len(sequence), value_type)
1✔
1083

1084
    def convert_dict_literal(self, dictionary: dict):
1✔
1085
        """
1086
        Converts a dict value
1087

1088
        :param dictionary: the value to be converted
1089
        """
1090
        value_type = Type.dict.build(dictionary)
1✔
1091
        self.convert_new_map(value_type)
1✔
1092

1093
        for key, value in dictionary.items():
1✔
1094
            self.duplicate_stack_top_item()
1✔
1095
            self.convert_literal(key)
1✔
1096
            value_start = self.convert_literal(value)
1✔
1097
            self.convert_set_item(value_start)
1✔
1098

1099
    def convert_byte_array(self, array: bytes):
1✔
1100
        """
1101
        Converts a byte value
1102

1103
        :param array: the value to be converted
1104
        """
1105
        self.insert_push_data(array)
1✔
1106
        self.convert_cast(Type.bytearray if isinstance(array, bytearray)
1✔
1107
                          else Type.bytes)
1108

1109
    def insert_push_data(self, data: bytes):
1✔
1110
        """
1111
        Inserts a push data value
1112

1113
        :param data: the value to be converted
1114
        """
1115
        data_len: int = len(data)
1✔
1116
        if data_len <= OpcodeInfo.PUSHDATA1.max_data_len:
1✔
1117
            op_info = OpcodeInfo.PUSHDATA1
1✔
1118
        elif data_len <= OpcodeInfo.PUSHDATA2.max_data_len:
×
1119
            op_info = OpcodeInfo.PUSHDATA2
×
1120
        else:
1121
            op_info = OpcodeInfo.PUSHDATA4
×
1122

1123
        data = Integer(data_len).to_byte_array(min_length=op_info.data_len) + data
1✔
1124
        self.__insert1(op_info, data)
1✔
1125
        self._stack_append(Type.str)  # push data pushes a ByteString value in the stack
1✔
1126

1127
    def insert_none(self):
1✔
1128
        """
1129
        Converts None literal
1130
        """
1131
        self.__insert1(OpcodeInfo.PUSHNULL)
1✔
1132
        self._stack_append(Type.none)
1✔
1133

1134
    def convert_cast(self, value_type: IType, is_internal: bool = False):
1✔
1135
        """
1136
        Converts casting types in Neo VM
1137
        """
1138
        stack_top_type: IType = self._stack[-1]
1✔
1139
        if (not value_type.is_generic
1✔
1140
                and not stack_top_type.is_generic
1141
                and value_type.stack_item is not Type.any.stack_item):
1142

1143
            if is_internal or value_type.stack_item != stack_top_type.stack_item:
1✔
1144
                # converts only if the stack types are different
1145
                self.__insert1(OpcodeInfo.CONVERT, value_type.stack_item)
1✔
1146

1147
            # but changes the value internally
1148
            self._stack_pop()
1✔
1149
            self._stack_append(value_type)
1✔
1150

1151
    def convert_new_map(self, map_type: IType):
1✔
1152
        """
1153
        Converts the creation of a new map
1154

1155
        :param map_type: the Neo Boa type of the map
1156
        """
1157
        self.__insert1(OpcodeInfo.NEWMAP)
1✔
1158
        self._stack_append(map_type)
1✔
1159

1160
    def convert_new_empty_array(self, length: int, array_type: IType):
1✔
1161
        """
1162
        Converts the creation of a new empty array
1163

1164
        :param length: the size of the new array
1165
        :param array_type: the Neo Boa type of the array
1166
        """
1167
        if length <= 0:
1✔
1168
            self.__insert1(OpcodeInfo.NEWARRAY0)
1✔
1169
        else:
1170
            self.convert_literal(length)
1✔
1171
            self._stack_pop()
1✔
1172
            self.__insert1(OpcodeInfo.NEWARRAY)
1✔
1173
        self._stack_append(array_type)
1✔
1174

1175
    def convert_new_array(self, length: int, array_type: IType = Type.list):
1✔
1176
        """
1177
        Converts the creation of a new array
1178

1179
        :param length: the size of the new array
1180
        :param array_type: the Neo Boa type of the array
1181
        """
1182
        if length <= 0:
1✔
1183
            self.convert_new_empty_array(length, array_type)
1✔
1184
        else:
1185
            self.convert_literal(length)
1✔
1186
            if array_type.stack_item is StackItemType.Struct:
1✔
1187
                self.__insert1(OpcodeInfo.PACKSTRUCT)
×
1188
            else:
1189
                self.__insert1(OpcodeInfo.PACK)
1✔
1190
            self._stack_pop()  # array size
1✔
1191
            for x in range(length):
1✔
1192
                self._stack_pop()
1✔
1193
            self._stack_append(array_type)
1✔
1194

1195
    def _set_array_item(self, value_start_address: int, check_for_negative_index: bool = True):
1✔
1196
        """
1197
        Converts the end of setting af a value in an array
1198
        """
1199
        index_type: IType = self._stack[-2]  # top: index
1✔
1200
        if index_type is Type.int and check_for_negative_index:
1✔
1201
            self.fix_negative_index(value_start_address)
1✔
1202

1203
    def convert_set_item(self, value_start_address: int, index_inserted_internally: bool = False):
1✔
1204
        """
1205
        Converts the end of setting af a value in an array
1206
        """
1207
        item_type: IType = self._stack[-3]  # top: index, 2nd-to-top: value, 3nd-to-top: array or map
1✔
1208
        if item_type.stack_item is not StackItemType.Map:
1✔
1209
            self._set_array_item(value_start_address, check_for_negative_index=not index_inserted_internally)
1✔
1210

1211
        self.__insert1(OpcodeInfo.SETITEM)
1✔
1212
        self._stack_pop()  # value
1✔
1213
        self._stack_pop()  # index
1✔
1214
        self._stack_pop()  # array or map
1✔
1215

1216
    def _get_array_item(self, check_for_negative_index: bool = True, test_is_negative_index=True):
1✔
1217
        """
1218
        Converts the end of get a value in an array
1219
        """
1220
        index_type: IType = self._stack[-1]  # top: index
1✔
1221
        if index_type is Type.int and check_for_negative_index:
1✔
1222
            self.fix_negative_index(test_is_negative=test_is_negative_index)
1✔
1223

1224
    def convert_get_item(self, index_inserted_internally: bool = False, index_is_positive=False, test_is_negative_index=True):
1✔
1225
        array_or_map_type: IType = self._stack[-2]  # second-to-top: array or map
1✔
1226
        if array_or_map_type.stack_item is not StackItemType.Map:
1✔
1227
            self._get_array_item(check_for_negative_index=not (index_inserted_internally or index_is_positive),
1✔
1228
                                 test_is_negative_index=test_is_negative_index)
1229

1230
        if array_or_map_type is Type.str:
1✔
1231
            self.convert_literal(1)  # length of substring
1✔
1232
            self.convert_get_substring(is_internal=index_inserted_internally or index_is_positive or not test_is_negative_index)
1✔
1233
        else:
1234
            self.__insert1(OpcodeInfo.PICKITEM)
1✔
1235
            self._stack_pop()
1✔
1236
            self._stack_pop()
1✔
1237
            if hasattr(array_or_map_type, 'value_type'):
1✔
1238
                new_stack_item = array_or_map_type.value_type
1✔
1239
            else:
1240
                new_stack_item = Type.any
1✔
1241
            self._stack_append(new_stack_item)
1✔
1242

1243
    def convert_get_substring(self, *, is_internal: bool = False, fix_result_type: bool = True):
1✔
1244
        """
1245
        Converts the end of get a substring
1246

1247
        :param is_internal: whether it was called when generating other implemented symbols
1248
        """
1249
        if not is_internal:
1✔
1250
            # if given substring size is negative, return empty string
1251
            self.duplicate_stack_top_item()
1✔
1252
            self.convert_literal(0)
1✔
1253
            self.convert_operation(BinaryOp.GtE)
1✔
1254

1255
            self._insert_jump(OpcodeInfo.JMPIF)
1✔
1256
            jmp_address = self.last_code_start_address
1✔
1257
            self.remove_stack_top_item()
1✔
1258
            self.convert_literal(0)
1✔
1259

1260
        self._stack_pop()  # length
1✔
1261
        self._stack_pop()  # start
1✔
1262
        original = self._stack_pop()  # original string
1✔
1263

1264
        self.__insert1(OpcodeInfo.SUBSTR)
1✔
1265
        if not is_internal:
1✔
1266
            self._update_jump(jmp_address, self.last_code_start_address)
1✔
1267
        self._stack_append(BufferType)  # substr returns a buffer instead of a bytestring
1✔
1268
        if fix_result_type:
1✔
1269
            self.convert_cast(original)
1✔
1270

1271
    def convert_get_array_slice(self, array: SequenceType):
1✔
1272
        """
1273
        Converts the end of get a substring
1274
        """
1275
        self.convert_new_empty_array(0, array)      # slice = []
1✔
1276
        self.duplicate_stack_item(3)                # index = slice_start
1✔
1277

1278
        start_jump = self.convert_begin_while()  # while index < slice_end
1✔
1279
        self.duplicate_stack_top_item()             # if index >= slice_start
1✔
1280
        self.duplicate_stack_item(5)
1✔
1281
        self.convert_operation(BinaryOp.GtE)
1✔
1282
        is_valid_index = self.convert_begin_if()
1✔
1283

1284
        self.duplicate_stack_item(2)                    # slice.append(array[index])
1✔
1285
        self.duplicate_stack_item(6)
1✔
1286
        self.duplicate_stack_item(3)
1✔
1287
        self.convert_get_item()
1✔
1288
        self.convert_builtin_method_call(Builtin.SequenceAppend.build(array))
1✔
1289
        self.convert_end_if(is_valid_index)
1✔
1290

1291
        self.__insert1(OpcodeInfo.INC)              # index += 1
1✔
1292

1293
        condition_address = VMCodeMapping.instance().bytecode_size
1✔
1294
        self.duplicate_stack_top_item()         # end while index < slice_end
1✔
1295
        self.duplicate_stack_item(4)
1✔
1296
        self.convert_operation(BinaryOp.Lt)
1✔
1297
        self.convert_end_while(start_jump, condition_address)
1✔
1298

1299
        self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
1✔
1300
        self.remove_stack_top_item()        # removes from the stack the arguments and the index
1✔
1301
        self.swap_reverse_stack_items(4)    # doesn't use CLEAR opcode because this would delete
1✔
1302
        self.remove_stack_top_item()        # data from external scopes
1✔
1303
        self.remove_stack_top_item()
1✔
1304
        self.remove_stack_top_item()
1✔
1305

1306
    def convert_get_sub_sequence(self):
1✔
1307
        """
1308
        Gets a slice of an array or ByteString
1309
        """
1310
        # top: length, index, array
1311
        if len(self._stack) > 2 and isinstance(self._stack[-3], SequenceType):
1✔
1312

1313
            if self._stack[-3].stack_item in (StackItemType.ByteString,
1✔
1314
                                              StackItemType.Buffer):
1315
                self.duplicate_stack_item(2)
1✔
1316
                self.convert_operation(BinaryOp.Sub)
1✔
1317
                self.convert_get_substring()
1✔
1318
            else:
1319
                array = self._stack[-3]
1✔
1320
                self.convert_get_array_slice(array)
1✔
1321

1322
    def convert_get_sequence_beginning(self):
1✔
1323
        """
1324
        Gets the beginning slice of an array or ByteString
1325
        """
1326
        if len(self._stack) > 1 and isinstance(self._stack[-2], SequenceType):
1✔
1327
            if self._stack[-2].stack_item in (StackItemType.ByteString,
1✔
1328
                                              StackItemType.Buffer):
1329
                self.__insert1(OpcodeInfo.LEFT)
1✔
1330
                self._stack_pop()  # length
1✔
1331
                original_type = self._stack_pop()  # original array
1✔
1332
                self._stack_append(BufferType)  # left returns a buffer instead of a bytestring
1✔
1333
                self.convert_cast(original_type)
1✔
1334
            else:
1335
                array = self._stack[-2]
1✔
1336

1337
                self.convert_literal(0)
1✔
1338
                self.swap_reverse_stack_items(2)
1✔
1339
                self.convert_get_array_slice(array)
1✔
1340

1341
    def convert_get_sequence_ending(self):
1✔
1342
        """
1343
        Gets the ending slice of an array or ByteString
1344
        """
1345
        # top: array, start_slice
1346
        if len(self._stack) > 1 and isinstance(self._stack[-2], SequenceType):
1✔
1347
            if self._stack[-2].stack_item in (StackItemType.ByteString,
1✔
1348
                                              StackItemType.Buffer):
1349
                self.duplicate_stack_item(2)
1✔
1350
                self.convert_builtin_method_call(Builtin.Len)
1✔
1351
                self.swap_reverse_stack_items(2)
1✔
1352
                self.convert_operation(BinaryOp.Sub)    # gets the amount of chars that should be taken after the index
1✔
1353
                self.__insert1(OpcodeInfo.RIGHT)
1✔
1354
                self._stack_pop()  # length
1✔
1355
                original_type = self._stack_pop()  # original array
1✔
1356
                self._stack_append(BufferType)     # right returns a buffer instead of a bytestring
1✔
1357
                self.convert_cast(original_type)
1✔
1358
            else:
1359
                array = self._stack[-2]
1✔
1360

1361
                self.duplicate_stack_item(2)
1✔
1362
                self.convert_builtin_method_call(Builtin.Len)
1✔
1363

1364
                self.convert_get_array_slice(array)
1✔
1365

1366
    def convert_copy(self):
1✔
1367
        if self._stack[-1].stack_item is StackItemType.Array:
1✔
1368
            self.__insert1(OpcodeInfo.UNPACK)
1✔
1369
            self.__insert1(OpcodeInfo.PACK)    # creates a new array with the values
1✔
1370

1371
    def convert_get_stride(self):
1✔
1372
        if len(self._stack) > 1 and isinstance(self._stack[-2], SequenceType):
1✔
1373
            if self._stack[-2].stack_item in (StackItemType.ByteString, StackItemType.Buffer):
1✔
1374
                self.convert_get_substring_stride()
1✔
1375
            else:
1376
                self.convert_get_array_stride()
1✔
1377

1378
    def convert_array_negative_stride(self):
1✔
1379
        """
1380
        Converts an array to its reverse, to be able to scroll through the reversed list
1381
        """
1382
        # The logic on this function only do variable[::-z]
1383

1384
        original = self._stack[-1]
1✔
1385
        self.convert_builtin_method_call(Builtin.Reversed)
1✔
1386
        self.convert_cast(ListType())
1✔
1387
        if isinstance(original, (StrType, BytesType)):        # if self was a string/bytes, then concat the values in the array
1✔
1388
            self.duplicate_stack_top_item()
1✔
1389
            self.convert_builtin_method_call(Builtin.Len)       # index = len(array) - 1
1✔
1390
            self.convert_literal('')                            # string = ''
1✔
1391

1392
            start_jump = self.convert_begin_while()
1✔
1393
            self.duplicate_stack_item(3)
1✔
1394
            self.duplicate_stack_item(3)
1✔
1395
            str_type = self._stack[-3]
1✔
1396
            self.__insert1(OpcodeInfo.PICKITEM)
1✔
1397
            self._stack_pop()
1✔
1398
            self._stack_pop()
1✔
1399
            self._stack_append(str_type)
1✔
1400
            self.swap_reverse_stack_items(2)
1✔
1401
            self.convert_operation(BinaryOp.Concat)             # string = string + array[index]
1✔
1402

1403
            condition_address = VMCodeMapping.instance().bytecode_size
1✔
1404
            self.swap_reverse_stack_items(2)
1✔
1405
            self.__insert1(OpcodeInfo.DEC)                      # index--
1✔
1406
            self.swap_reverse_stack_items(2)
1✔
1407
            self.duplicate_stack_item(2)
1✔
1408
            self.convert_literal(0)
1✔
1409
            self.convert_operation(BinaryOp.GtE)                # if index <= 0, stop loop
1✔
1410
            self.convert_end_while(start_jump, condition_address)
1✔
1411
            self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
1✔
1412

1413
            # remove auxiliary values
1414
            self.swap_reverse_stack_items(3)
1✔
1415
            self.remove_stack_top_item()
1✔
1416
            self.remove_stack_top_item()
1✔
1417

1418
    def convert_get_substring_stride(self):
1✔
1419
        # initializing auxiliary variables
1420
        self.duplicate_stack_item(2)
1✔
1421
        self.convert_builtin_method_call(Builtin.Len)
1✔
1422
        self.convert_literal(0)                         # index = 0
1✔
1423
        self.convert_literal('')                        # substr = ''
1✔
1424

1425
        # logic verifying if substr[index] should be concatenated or not
1426
        start_jump = self.convert_begin_while()
1✔
1427
        self.duplicate_stack_item(2)
1✔
1428
        self.duplicate_stack_item(5)
1✔
1429
        self.convert_operation(BinaryOp.Mod)
1✔
1430
        self.convert_literal(0)
1✔
1431
        self.convert_operation(BinaryOp.NumEq)
1✔
1432
        is_mod_0 = self.convert_begin_if()              # if index % stride == 0, then concatenate it
1✔
1433

1434
        # concatenating substr[index] with substr
1435
        self.duplicate_stack_item(5)
1✔
1436
        self.duplicate_stack_item(3)
1✔
1437
        self.convert_literal(1)
1✔
1438
        self._stack_pop()  # length
1✔
1439
        self._stack_pop()  # start
1✔
1440
        str_type = self._stack_pop()
1✔
1441
        self.__insert1(OpcodeInfo.SUBSTR)
1✔
1442
        self._stack_append(BufferType)                  # SUBSTR returns a buffer instead of a bytestring
1✔
1443
        self.convert_cast(str_type)
1✔
1444
        self.convert_operation(BinaryOp.Concat)         # substr = substr + string[index]
1✔
1445
        self.convert_end_if(is_mod_0)
1✔
1446

1447
        # increment the index by 1
1448
        self.swap_reverse_stack_items(2)
1✔
1449
        self.__insert1(OpcodeInfo.INC)                  # index++
1✔
1450
        self.swap_reverse_stack_items(2)
1✔
1451

1452
        # verifying if it should still be in the while
1453
        condition_address = VMCodeMapping.instance().bytecode_size
1✔
1454
        self.duplicate_stack_item(2)
1✔
1455
        self.duplicate_stack_item(4)
1✔
1456
        self.convert_operation(BinaryOp.Lt)             # stop the loop when index >= len(str)
1✔
1457
        self.convert_end_while(start_jump, condition_address)
1✔
1458
        self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
1✔
1459

1460
        # removing auxiliary values
1461
        self.swap_reverse_stack_items(5)
1✔
1462
        self.remove_stack_top_item()
1✔
1463
        self.remove_stack_top_item()
1✔
1464
        self.remove_stack_top_item()
1✔
1465
        self.remove_stack_top_item()
1✔
1466

1467
    def convert_get_array_stride(self):
1✔
1468
        # initializing auxiliary variable
1469
        self.duplicate_stack_item(2)
1✔
1470
        self.convert_builtin_method_call(Builtin.Len)
1✔
1471
        self.__insert1(OpcodeInfo.DEC)                      # index = len(array) - 1
1✔
1472

1473
        # logic verifying if array[index] should be removed or not
1474
        start_jump = self.convert_begin_while()
1✔
1475
        self.duplicate_stack_item(2)
1✔
1476
        self.duplicate_stack_item(2)
1✔
1477
        self.swap_reverse_stack_items(2)
1✔
1478
        self.convert_operation(BinaryOp.Mod)
1✔
1479
        self.convert_literal(0)
1✔
1480
        self.convert_operation(BinaryOp.NumNotEq)
1✔
1481
        is_not_mod_0 = self.convert_begin_if()              # if index % stride != 0, then remove it
1✔
1482

1483
        # removing element from array
1484
        self.duplicate_stack_item(3)
1✔
1485
        self.duplicate_stack_item(2)
1✔
1486
        self.__insert1(OpcodeInfo.REMOVE)                   # array.pop(index)
1✔
1487
        self._stack_pop()
1✔
1488
        self._stack_pop()
1✔
1489
        self.convert_end_if(is_not_mod_0)
1✔
1490

1491
        # decrement 1 from index
1492
        self.__insert1(OpcodeInfo.DEC)
1✔
1493

1494
        # verifying if it should still be in the while
1495
        condition_address = VMCodeMapping.instance().bytecode_size
1✔
1496
        self.duplicate_stack_top_item()
1✔
1497
        self.__insert1(OpcodeInfo.SIGN)
1✔
1498
        self.convert_literal(-1)
1✔
1499
        self.convert_operation(BinaryOp.NumNotEq)       # stop the loop when index < 0
1✔
1500
        self.convert_end_while(start_jump, condition_address)
1✔
1501
        self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
1✔
1502

1503
        # removing auxiliary values
1504
        self.remove_stack_top_item()                    # removed index from stack
1✔
1505
        self.remove_stack_top_item()                    # removed stride from stack
1✔
1506

1507
    def convert_starred_variable(self):
1✔
1508
        top_stack_item = self._stack[-1].stack_item
1✔
1509
        if top_stack_item is StackItemType.Array:
1✔
1510
            self.convert_copy()
1✔
1511
        elif top_stack_item is StackItemType.Map:
×
1512
            self.convert_builtin_method_call(Builtin.DictKeys)
×
1513
        else:
1514
            return
×
1515

1516
        self.convert_cast(Type.tuple)
1✔
1517

1518
    def convert_load_symbol(self, symbol_id: str, params_addresses: List[int] = None, is_internal: bool = False,
1✔
1519
                            class_type: Optional[UserClass] = None):
1520
        """
1521
        Converts the load of a symbol
1522

1523
        :param symbol_id: the symbol identifier
1524
        :param params_addresses: a list with each function arguments' first addresses
1525
        """
1526
        another_symbol_id, symbol = self.get_symbol(symbol_id, is_internal=is_internal)
1✔
1527

1528
        if class_type is not None and symbol_id in class_type.symbols:
1✔
1529
            symbol = class_type.symbols[symbol_id]
1✔
1530

1531
        if symbol is not Type.none:
1✔
1532
            if isinstance(symbol, Property):
1✔
1533
                symbol = symbol.getter
1✔
1534
                params_addresses = []
1✔
1535
            elif isinstance(symbol, ClassType) and params_addresses is not None:
1✔
1536
                symbol = symbol.constructor_method()
1✔
1537

1538
            if not params_addresses:
1✔
1539
                params_addresses = []
1✔
1540

1541
            if isinstance(symbol, Variable):
1✔
1542
                symbol_id = another_symbol_id
1✔
1543
                self.convert_load_variable(symbol_id, symbol, class_type)
1✔
1544
            elif isinstance(symbol, IBuiltinMethod) and symbol.body is None:
1✔
1545
                self.convert_builtin_method_call(symbol, params_addresses)
1✔
1546
            elif isinstance(symbol, Event):
1✔
1547
                self.convert_event_call(symbol)
1✔
1548
            elif isinstance(symbol, Method):
1✔
1549
                self.convert_method_call(symbol, len(params_addresses))
1✔
1550
            elif isinstance(symbol, UserClass):
1✔
1551
                self.convert_class_symbol(symbol, symbol_id)
1✔
1552

1553
    def convert_load_class_variable(self, class_type: ClassType, var_id: str, is_internal: bool = False):
1✔
1554
        variable_list = (class_type._all_variables
1✔
1555
                         if hasattr(class_type, '_all_variables') and is_internal
1556
                         else class_type.variables)
1557

1558
        if var_id in variable_list:
1✔
1559
            var = variable_list[var_id]
1✔
1560
            index = list(variable_list).index(var_id)
1✔
1561
            self.convert_literal(index)
1✔
1562
            self.convert_get_item(index_inserted_internally=True)
1✔
1563
            self._stack_pop()  # pop class type
1✔
1564
            self._stack_append(var.type)  # push variable type
1✔
1565

1566
    def convert_load_variable(self, var_id: str, var: Variable, class_type: Optional[UserClass] = None):
1✔
1567
        """
1568
        Converts the assignment of a variable
1569

1570
        :param var_id: the value to be converted
1571
        :param var: the actual variable to be loaded
1572
        """
1573
        index, local, is_arg = self._get_variable_info(var_id)
1✔
1574
        if index >= 0:
1✔
1575
            opcode = OpcodeHelper.get_load(index, local, is_arg)
1✔
1576
            op_info = OpcodeInfo.get_info(opcode)
1✔
1577

1578
            if op_info.data_len > 0:
1✔
1579
                self.__insert1(op_info, Integer(index).to_byte_array())
1✔
1580
            else:
1581
                self.__insert1(op_info)
1✔
1582
            self._stack_append(var.type)
1✔
1583

1584
        elif hasattr(var.type, 'get_value'):
1✔
1585
            # the variable is a type constant
1586
            # TODO: change this when implement class conversion
1587
            value = var.type.get_value(var_id.split(constants.ATTRIBUTE_NAME_SEPARATOR)[-1])
1✔
1588
            if value is not None:
1✔
1589
                self.convert_literal(value)
1✔
1590

1591
        elif var_id in self._globals:
1✔
1592
            another_var_id, var = self.get_symbol(var_id)
1✔
1593
            storage_key = codegenerator.get_storage_key_for_variable(var)
1✔
1594
            self._convert_builtin_storage_get_or_put(True, storage_key)
1✔
1595

1596
        elif class_type:
1✔
1597
            self.convert_load_class_variable(class_type, var_id)
1✔
1598

1599
    def convert_store_variable(self, var_id: str, value_start_address: int = None, user_class: UserClass = None):
1✔
1600
        """
1601
        Converts the assignment of a variable
1602

1603
        :param var_id: the value to be converted
1604
        """
1605
        inner_index = None
1✔
1606
        index, local, is_arg = self._get_variable_info(var_id)
1✔
1607

1608
        if user_class is None and len(self._stack) > 1 and isinstance(self._stack[-2], UserClass):
1✔
1609
            user_class = self._stack[-2]
1✔
1610

1611
        if isinstance(user_class, UserClass) and var_id in user_class.variables:
1✔
1612
            index, local, is_arg = self._get_variable_info(user_class.identifier)
1✔
1613
            inner_index = list(user_class.variables).index(var_id)
1✔
1614

1615
        if isinstance(inner_index, int):
1✔
1616
            # it's a variable from a class
1617
            self.convert_literal(inner_index)
1✔
1618
            index_address = self.bytecode_size
1✔
1619

1620
            if var_id in user_class.class_variables:
1✔
1621
                # it's a class variable
1622
                self.convert_load_variable(user_class.identifier, Variable(user_class))
1✔
1623
                no_stack_items_to_swap = 3
1✔
1624
            else:
1625
                no_stack_items_to_swap = 2
1✔
1626

1627
            self.swap_reverse_stack_items(no_stack_items_to_swap)
1✔
1628
            self.convert_set_item(index_address, index_inserted_internally=True)
1✔
1629
            return
1✔
1630

1631
        if index >= 0:
1✔
1632
            opcode = OpcodeHelper.get_store(index, local, is_arg)
1✔
1633
            if opcode is not None:
1✔
1634
                op_info = OpcodeInfo.get_info(opcode)
1✔
1635

1636
                if op_info.data_len > 0:
1✔
1637
                    self.__insert1(op_info, Integer(index).to_byte_array())
1✔
1638
                else:
1639
                    self.__insert1(op_info)
1✔
1640
                stored_type = self._stack_pop()
1✔
1641

1642
                from boa3.internal.analyser.model.optimizer import UndefinedType
1✔
1643
                if (var_id in self._current_scope.symbols or
1✔
1644
                        (var_id in self._locals and self._current_method.locals[var_id].type is UndefinedType)):
1645
                    another_symbol_id, symbol = self.get_symbol(var_id)
1✔
1646
                    if isinstance(symbol, Variable):
1✔
1647
                        var = symbol.copy()
1✔
1648
                        var.set_type(stored_type)
1✔
1649
                        self._current_scope.include_symbol(var_id, var)
1✔
1650

1651
        elif var_id in self._globals:
1✔
1652
            another_var_id, var = self.get_symbol(var_id)
1✔
1653
            storage_key = codegenerator.get_storage_key_for_variable(var)
1✔
1654
            if value_start_address is None:
1✔
1655
                value_start_address = self.bytecode_size
×
1656
            self._convert_builtin_storage_get_or_put(False, storage_key, value_start_address)
1✔
1657

1658
    def _convert_builtin_storage_get_or_put(self, is_get: bool, storage_key: bytes, arg_address: int = None):
1✔
1659
        addresses = [arg_address] if arg_address is not None else [self.bytecode_size]
1✔
1660
        if not is_get:
1✔
1661
            # must serialized before storing the value
1662
            self.convert_builtin_method_call(Interop.Serialize, addresses)
1✔
1663

1664
        self.convert_literal(storage_key)
1✔
1665
        self.convert_builtin_method_call(Interop.StorageGetContext)
1✔
1666

1667
        builtin_method = Interop.StorageGet if is_get else Interop.StoragePut
1✔
1668
        self.convert_builtin_method_call(builtin_method)
1✔
1669

1670
        if is_get:
1✔
1671
            # once the value is retrieved, it must be deserialized
1672
            self.convert_builtin_method_call(Interop.Deserialize, addresses)
1✔
1673

1674
    def _get_variable_info(self, var_id: str) -> Tuple[int, bool, bool]:
1✔
1675
        """
1676
        Gets the necessary information about the variable to get the correct opcode
1677

1678
        :param var_id: the name id of the
1679
        :return: returns the index of the variable in its scope and two boolean variables for representing the
1680
        variable scope:
1681
            `local` is True if it is a local variable and
1682
            `is_arg` is True only if the variable is a parameter of the function.
1683
        If the variable is not found, returns (-1, False, False)
1684
        """
1685
        is_arg: bool = False
1✔
1686
        local: bool = False
1✔
1687
        scope = None
1✔
1688

1689
        if var_id in self._args:
1✔
1690
            is_arg: bool = True
1✔
1691
            local: bool = True
1✔
1692
            scope = self._args
1✔
1693
        elif var_id in self._locals:
1✔
1694
            is_arg = False
1✔
1695
            local: bool = True
1✔
1696
            scope = self._locals
1✔
1697
        elif var_id in self._statics:
1✔
1698
            scope = self._statics
1✔
1699

1700
        if scope is not None:
1✔
1701
            index: int = scope.index(var_id) if var_id in scope else -1
1✔
1702
        else:
1703
            index = -1
1✔
1704

1705
        return index, local, is_arg
1✔
1706

1707
    def convert_builtin_method_call(self, function: IBuiltinMethod, args_address: List[int] = None, is_internal: bool = False):
1✔
1708
        """
1709
        Converts a builtin method function call
1710

1711
        :param function: the function to be converted
1712
        :param args_address: a list with each function arguments' first addresses
1713
        :param is_internal: whether it was called when generating other implemented symbols
1714
        """
1715
        stack_before = len(self._stack)
1✔
1716
        if args_address is None:
1✔
1717
            args_address = []
1✔
1718
        store_opcode: OpcodeInformation = None
1✔
1719
        store_data: bytes = b''
1✔
1720

1721
        if function.pack_arguments:
1✔
1722
            self.convert_new_array(len(args_address))
×
1723

1724
        if function.stores_on_slot and 0 < len(function.args) <= len(args_address):
1✔
1725
            address = args_address[-len(function.args)]
1✔
1726
            load_instr = VMCodeMapping.instance().code_map[address]
1✔
1727
            if OpcodeHelper.is_load_slot(load_instr.opcode):
1✔
1728
                store: Opcode = OpcodeHelper.get_store_from_load(load_instr.opcode)
1✔
1729
                store_opcode = OpcodeInfo.get_info(store)
1✔
1730
                store_data = load_instr.data
1✔
1731

1732
        fix_negatives = function.validate_negative_arguments()
1✔
1733
        if len(fix_negatives) > 0:
1✔
1734
            args_end_addresses = args_address[1:]
1✔
1735
            args_end_addresses.append(self.bytecode_size)
1✔
1736

1737
            if function.push_self_first():
1✔
1738
                addresses = args_end_addresses[:1] + list(reversed(args_end_addresses[1:]))
1✔
1739
            else:
1740
                addresses = list(reversed(args_end_addresses))
×
1741

1742
            for arg in sorted(fix_negatives, reverse=True):
1✔
1743
                if len(addresses) > arg:
1✔
1744
                    self.fix_negative_index(addresses[arg])
1✔
1745

1746
        self._convert_builtin_call(function, previous_stack_size=stack_before, is_internal=is_internal)
1✔
1747

1748
        if store_opcode is not None:
1✔
1749
            self._insert_jump(OpcodeInfo.JMP)
1✔
1750
            self._update_codes_without_target_to_next(self.last_code_start_address)
1✔
1751
            jump = self.last_code_start_address
1✔
1752
            self.__insert1(store_opcode, store_data)
1✔
1753
            self._update_jump(jump, VMCodeMapping.instance().bytecode_size)
1✔
1754

1755
    def _convert_builtin_call(self, builtin: IBuiltinCallable, previous_stack_size: int = None, is_internal: bool = False):
1✔
1756
        if not isinstance(previous_stack_size, int):
1✔
1757
            previous_stack_size = len(self._stack)
1✔
1758
        elif previous_stack_size < 0:
1✔
1759
            previous_stack_size = 0
×
1760

1761
        size_before_generating = self.bytecode_size
1✔
1762
        if is_internal:
1✔
1763
            builtin.generate_internal_opcodes(self)
1✔
1764
        else:
1765
            builtin.generate_opcodes(self)
1✔
1766

1767
        if size_before_generating == self.bytecode_size:
1✔
1768
            # TODO: remove this when the built in code generation refactoring is finished
1769
            for opcode, data in builtin.opcode:
1✔
1770
                op_info = OpcodeInfo.get_info(opcode)
×
1771
                if opcode is Opcode.CALL and isinstance(data, Method):
×
1772
                    # avoid losing current stack state
1773
                    for _ in data.args:
×
1774
                        self._stack_append(Type.any)
×
1775
                    self.convert_method_call(data, len(data.args) - 1)
×
1776
                else:
1777
                    self.__insert1(op_info, data)
×
1778

1779
        if isinstance(builtin, IBuiltinMethod):
1✔
1780
            if is_internal and hasattr(builtin, 'internal_call_args'):
1✔
1781
                expected_stack_after = previous_stack_size - builtin.internal_call_args
1✔
1782
            else:
1783
                expected_stack_after = previous_stack_size - builtin.args_on_stack
1✔
1784
        else:
1785
            expected_stack_after = previous_stack_size - len(builtin.args)
1✔
1786

1787
        if expected_stack_after < 0:
1✔
1788
            expected_stack_after = 0
×
1789

1790
        while expected_stack_after < len(self._stack):
1✔
1791
            self._stack_pop()
1✔
1792
        if builtin.return_type not in (None, Type.none):
1✔
1793
            self._stack_append(builtin.return_type)
1✔
1794

1795
    def convert_method_call(self, function: Method, num_args: int):
1✔
1796
        """
1797
        Converts a method function call
1798

1799
        :param function: the function to be converted
1800
        """
1801
        if function.is_init:
1✔
1802
            if num_args == len(function.args):
1✔
1803
                self.remove_stack_top_item()
×
1804
                num_args -= 1
×
1805

1806
            if num_args == len(function.args) - 1:
1✔
1807
                # if this method is a constructor and only the self argument is missing
1808
                function_result = function.type
1✔
1809
                size = len(function_result.variables) if isinstance(function_result, UserClass) else 0
1✔
1810
                if self.stack_size < 1 or not self._stack[-1].is_type_of(function_result):
1✔
1811
                    self.convert_new_empty_array(size, function_result)
×
1812

1813
        if isinstance(function.origin_class, ContractInterfaceClass):
1✔
1814
            if function.external_name is not None:
1✔
1815
                function_id = function.external_name
1✔
1816
            else:
1817
                function_id = next((symbol_id
1✔
1818
                                    for symbol_id, symbol in function.origin_class.symbols.items()
1819
                                    if symbol is function),
1820
                                   None)
1821
            self._add_to_metadata_permissions(function.origin_class, function_id)
1✔
1822

1823
            if isinstance(function_id, str):
1✔
1824
                self.convert_new_array(len(function.args))
1✔
1825
                self.convert_literal(Interop.CallFlagsType.default_value)
1✔
1826
                self.convert_literal(function_id)
1✔
1827
                self.convert_literal(function.origin_class.contract_hash.to_array())
1✔
1828
                self.convert_builtin_method_call(Interop.CallContract)
1✔
1829
                self._stack_pop()  # remove call contract 'any' result from the stack
1✔
1830
                self._stack_append(function.return_type)    # add the return type on the stack even if it is None
1✔
1831

1832
        else:
1833
            from boa3.internal.neo.vm.CallCode import CallCode
1✔
1834
            call_code = CallCode(function)
1✔
1835
            self.__insert_code(call_code)
1✔
1836
            self._update_codes_with_target(call_code)
1✔
1837

1838
            for arg in range(num_args):
1✔
1839
                self._stack_pop()
1✔
1840
            if function.is_init:
1✔
1841
                self._stack_pop()  # pop duplicated result if it's init
1✔
1842

1843
            if function.return_type is not Type.none:
1✔
1844
                self._stack_append(function.return_type)
1✔
1845

1846
    def convert_method_token_call(self, method_token_id: int):
1✔
1847
        """
1848
        Converts a method token call
1849
        """
1850
        if method_token_id >= 0:
1✔
1851
            method_token = VMCodeMapping.instance().get_method_token(method_token_id)
1✔
1852
            if method_token is not None:
1✔
1853
                opcode_info = OpcodeInfo.CALLT
1✔
1854
                self.__insert1(opcode_info, Integer(method_token_id).to_byte_array(min_length=opcode_info.data_len))
1✔
1855

1856
    def convert_event_call(self, event: Event):
1✔
1857
        """
1858
        Converts an event call
1859

1860
        :param event_id: called event identifier
1861
        :param event: called event
1862
        """
1863
        self.convert_new_array(len(event.args_to_generate), Type.list)
1✔
1864
        if event.generate_name:
1✔
1865
            self.convert_literal(event.name)
1✔
1866
        else:
1867
            self.swap_reverse_stack_items(2)
1✔
1868

1869
        from boa3.internal.model.builtin.interop.interop import Interop
1✔
1870
        self._convert_builtin_call(Interop.Notify, is_internal=True)
1✔
1871

1872
    def convert_class_symbol(self, class_type: ClassType, symbol_id: str, load: bool = True) -> Optional[int]:
1✔
1873
        """
1874
        Converts an class symbol
1875

1876
        :param class_type:
1877
        :param symbol_id:
1878
        :param load:
1879
        """
1880
        method: Method
1881
        is_safe_to_convert = False
1✔
1882

1883
        if symbol_id in class_type.variables:
1✔
1884
            return self.convert_class_variable(class_type, symbol_id, load)
1✔
1885
        elif symbol_id in class_type.properties:
1✔
1886
            symbol = class_type.properties[symbol_id]
1✔
1887
            is_safe_to_convert = True
1✔
1888
            method = symbol.getter if load else symbol.setter
1✔
1889
        elif symbol_id in class_type.instance_methods:
1✔
1890
            method = class_type.instance_methods[symbol_id]
1✔
1891
        elif symbol_id in class_type.class_methods:
1✔
1892
            method = class_type.class_methods[symbol_id]
1✔
1893
        elif isinstance(class_type, UserClass):
1✔
1894
            return self.convert_user_class(class_type, symbol_id)
1✔
1895
        else:
1896
            return
×
1897

1898
        if isinstance(method, IBuiltinMethod):
1✔
1899
            if not is_safe_to_convert:
1✔
1900
                is_safe_to_convert = len(method.args) == 0
1✔
1901

1902
            if is_safe_to_convert:
1✔
1903
                self.convert_builtin_method_call(method)
1✔
1904
        else:
1905
            self.convert_method_call(method, 0)
×
1906
        return symbol_id
1✔
1907

1908
    def convert_user_class(self, class_type: UserClass, symbol_id: str) -> Optional[int]:
1✔
1909
        """
1910
        Converts an class symbol
1911

1912
        :param class_type:
1913
        :param symbol_id:
1914
        """
1915
        start_address = self.bytecode_size
1✔
1916

1917
        if symbol_id in self._statics:
1✔
1918
            self.convert_load_variable(symbol_id, Variable(class_type))
1✔
1919
        else:
1920
            # TODO: change to create an array with the class variables' default values when they are implemented
1921
            self.convert_new_empty_array(len(class_type.class_variables), class_type)
1✔
1922

1923
        return start_address
1✔
1924

1925
    def convert_class_variable(self, class_type: ClassType, symbol_id: str, load: bool = True):
1✔
1926
        """
1927
        Converts an class variable
1928

1929
        :param class_type:
1930
        :param symbol_id:
1931
        :param load:
1932
        """
1933
        if symbol_id in class_type.variables:
1✔
1934
            index = list(class_type.variables).index(symbol_id)
1✔
1935

1936
            if load:
1✔
1937
                self.convert_literal(index)
1✔
1938
                self.convert_get_item(index_inserted_internally=True)
1✔
1939

1940
                symbol_type = class_type.variables[symbol_id].type
1✔
1941
                if self._stack[-1] != symbol_type:
1✔
1942
                    self._stack_pop()
1✔
1943
                    self._stack_append(symbol_type)
1✔
1944

1945
            return index
1✔
1946

1947
    def convert_operation(self, operation: IOperation, is_internal: bool = False):
1✔
1948
        """
1949
        Converts an operation
1950

1951
        :param operation: the operation that will be converted
1952
        :param is_internal: whether it was called when generating other implemented symbols
1953
        """
1954
        stack_before = len(self._stack)
1✔
1955
        if is_internal:
1✔
1956
            operation.generate_internal_opcodes(self)
1✔
1957
        else:
1958
            operation.generate_opcodes(self)
1✔
1959

1960
        expected_stack_after = stack_before - operation.op_on_stack
1✔
1961
        if expected_stack_after < 0:
1✔
1962
            expected_stack_after = 0
×
1963

1964
        while expected_stack_after < len(self._stack):
1✔
1965
            self._stack_pop()
1✔
1966
        self._stack_append(operation.result)
1✔
1967

1968
    def convert_assert(self, has_message: bool = False):
1✔
1969

1970
        if has_message:
1✔
1971
            asserted_type = self._stack[-2] if len(self._stack) > 1 else Type.any
1✔
1972
        else:
1973
            asserted_type = self._stack[-1] if len(self._stack) > 0 else Type.any
1✔
1974

1975
        if not isinstance(asserted_type, PrimitiveType):
1✔
1976
            if has_message:
1✔
1977
                self.swap_reverse_stack_items(2)
×
1978

1979
            len_pos = VMCodeMapping.instance().bytecode_size
1✔
1980
            # if the value is an array, a map or a struct, asserts it is not empty
1981
            self.convert_builtin_method_call(Builtin.Len)
1✔
1982
            len_code = VMCodeMapping.instance().code_map[len_pos]
1✔
1983

1984
            if asserted_type is Type.any:
1✔
1985
                # need to check in runtime
1986
                self.duplicate_stack_top_item()
1✔
1987
                self.insert_type_check(StackItemType.Array)
1✔
1988
                self._insert_jump(OpcodeInfo.JMPIF, len_code)
1✔
1989

1990
                self.duplicate_stack_top_item()
1✔
1991
                self.insert_type_check(StackItemType.Map)
1✔
1992
                self._insert_jump(OpcodeInfo.JMPIF, len_code)
1✔
1993

1994
                self.duplicate_stack_top_item()
1✔
1995
                self.insert_type_check(StackItemType.Struct)
1✔
1996
                self._insert_jump(OpcodeInfo.JMPIFNOT, 2)
1✔
1997

1998
                VMCodeMapping.instance().move_to_end(len_pos, len_pos)
1✔
1999
            if has_message:
1✔
2000
                self.swap_reverse_stack_items(2)
×
2001

2002
        self.__insert1(OpcodeInfo.ASSERT if not has_message else OpcodeInfo.ASSERTMSG)
1✔
2003
        if has_message and len(self._stack) > 1:
1✔
2004
            # pop message
2005
            self._stack_pop()
1✔
2006

2007
        if len(self._stack) > 0:
1✔
2008
            # pop assert test
2009
            self._stack_pop()
1✔
2010

2011
    def convert_abort(self, has_message: bool = False, *, is_internal: bool = False):
1✔
2012
        if not is_internal:
1✔
2013
            abort_msg_type = self._stack[-1] if len(self._stack) > 0 else Type.none
1✔
2014
            if has_message and Type.none.is_type_of(abort_msg_type):
1✔
2015
                # do not use ABORTMSG if we can detect the msg is None
2016
                has_message = not has_message
×
2017
                self._stack_pop()
×
2018

2019
        if not has_message:
1✔
2020
            self.__insert1(OpcodeInfo.ABORT)
×
2021
        else:
2022
            self.__insert1(OpcodeInfo.ABORTMSG)
1✔
2023
            self._stack_pop()
1✔
2024

2025
    def convert_new_exception(self, exception_args_len: int = 0):
1✔
2026
        if exception_args_len == 0 or len(self._stack) == 0:
1✔
2027
            self.convert_literal(Builtin.Exception.default_message)
1✔
2028

2029
        if exception_args_len > 1:
1✔
2030
            self.convert_new_array(exception_args_len)
×
2031

2032
        self._stack_pop()
1✔
2033
        self._stack_append(Type.exception)
1✔
2034

2035
    def convert_raise_exception(self):
1✔
2036
        if len(self._stack) == 0:
1✔
2037
            self.convert_literal(Builtin.Exception.default_message)
×
2038

2039
        self._stack_pop()
1✔
2040
        self.__insert1(OpcodeInfo.THROW)
1✔
2041

2042
    def insert_opcode(self, opcode: Opcode, data: bytes = None,
1✔
2043
                      add_to_stack: List[IType] = None, pop_from_stack: bool = False):
2044
        """
2045
        Inserts one opcode into the bytecode. Used to generate built-in symbols.
2046

2047
        :param opcode: info of the opcode  that will be inserted
2048
        :param data: data of the opcode, if needed
2049
        :param add_to_stack: expected data to be included on stack, if needed
2050
        :param pop_from_stack: if needs to update stack given opcode stack items
2051
        """
2052
        op_info = OpcodeInfo.get_info(opcode)
1✔
2053
        if op_info is not None:
1✔
2054
            self.__insert1(op_info, data)
1✔
2055
            if pop_from_stack:
1✔
2056
                for _ in range(op_info.stack_items):
1✔
2057
                    self._stack_pop()
1✔
2058

2059
        if isinstance(add_to_stack, list):
1✔
2060
            for stack_item in add_to_stack:
1✔
2061
                self._stack_append(stack_item)
1✔
2062

2063
    def insert_type_check(self, type_to_check: Optional[StackItemType]):
1✔
2064
        if isinstance(type_to_check, StackItemType):
1✔
2065
            self.__insert1(OpcodeInfo.ISTYPE, type_to_check)
1✔
2066
        else:
2067
            self.__insert1(OpcodeInfo.ISNULL)
1✔
2068
        self._stack_pop()
1✔
2069
        self._stack_append(Type.bool)
1✔
2070

2071
    def __insert1(self, op_info: OpcodeInformation, data: bytes = None):
1✔
2072
        """
2073
        Inserts one opcode into the bytecode
2074

2075
        :param op_info: info of the opcode  that will be inserted
2076
        :param data: data of the opcode, if needed
2077
        """
2078
        vm_code = VMCode(op_info, data)
1✔
2079

2080
        if OpcodeHelper.has_target(op_info.opcode):
1✔
2081
            data = vm_code.raw_data
1✔
2082
            relative_address: int = Integer.from_bytes(data, signed=True)
1✔
2083
            actual_address = VMCodeMapping.instance().bytecode_size + relative_address
1✔
2084
            if (self._can_append_target
1✔
2085
                    and relative_address != 0
2086
                    and actual_address in VMCodeMapping.instance().code_map):
2087
                vm_code.set_target(VMCodeMapping.instance().code_map[actual_address])
1✔
2088
            else:
2089
                self._include_missing_target(vm_code, actual_address)
1✔
2090

2091
        self.__insert_code(vm_code)
1✔
2092
        self._update_codes_with_target(vm_code)
1✔
2093

2094
    def __insert_code(self, vm_code: VMCode):
1✔
2095
        """
2096
        Inserts one vmcode into the bytecode
2097

2098
        :param vm_code: the opcode that will be inserted
2099
        """
2100
        VMCodeMapping.instance().insert_code(vm_code)
1✔
2101

2102
    def _include_missing_target(self, vmcode: VMCode, target_address: int = 0):
1✔
2103
        """
2104
        Includes a instruction which parameter is another instruction that wasn't converted yet
2105

2106
        :param vmcode: instruction with incomplete parameter
2107
        :param target_address: target instruction expected address
2108
        :return:
2109
        """
2110
        if OpcodeHelper.has_target(vmcode.opcode):
1✔
2111
            if target_address == VMCodeMapping.instance().bytecode_size:
1✔
2112
                target_address = None
1✔
2113
            else:
2114
                self._remove_missing_target(vmcode)
1✔
2115

2116
            if target_address not in self._missing_target:
1✔
2117
                self._missing_target[target_address] = []
1✔
2118
            if vmcode not in self._missing_target[target_address]:
1✔
2119
                self._missing_target[target_address].append(vmcode)
1✔
2120

2121
    def _remove_missing_target(self, vmcode: VMCode):
1✔
2122
        """
2123
        Removes a instruction from the missing target list
2124

2125
        :param vmcode: instruction with incomplete parameter
2126
        :return:
2127
        """
2128
        if OpcodeHelper.has_target(vmcode.opcode):
1✔
2129
            for target_address, opcodes in self._missing_target.copy().items():
1✔
2130
                if vmcode in opcodes:
1✔
2131
                    opcodes.remove(vmcode)
1✔
2132
                    if len(opcodes) == 0:
1✔
2133
                        self._missing_target.pop(target_address)
1✔
2134
                    break
1✔
2135

2136
    def _check_codes_with_target(self) -> bool:
1✔
2137
        """
2138
        Verifies if there are any instructions targeting positions not included yet.
2139
        """
2140
        instance = VMCodeMapping.instance()
1✔
2141
        current_bytecode_size = instance.bytecode_size
1✔
2142
        for target_address, codes in list(self._missing_target.items()):
1✔
2143
            if target_address is not None and target_address >= current_bytecode_size:
1✔
2144
                return True
×
2145

2146
        if None in self._missing_target:
1✔
2147
            for code in self._missing_target[None]:
1✔
2148
                if OpcodeHelper.is_jump(code.info.opcode) and code.target is None:
1✔
2149
                    target = Integer.from_bytes(code.raw_data) + VMCodeMapping.instance().get_start_address(code)
1✔
2150
                    if target >= current_bytecode_size:
1✔
2151
                        return True
1✔
2152
        return False
1✔
2153

2154
    def _update_codes_with_target(self, vm_code: VMCode):
1✔
2155
        """
2156
        Verifies if there are any instructions targeting the code. If it exists, updates each instruction found
2157

2158
        :param vm_code: targeted instruction
2159
        """
2160
        instance = VMCodeMapping.instance()
1✔
2161
        vm_code_start_address = instance.get_start_address(vm_code)
1✔
2162
        for target_address, codes in list(self._missing_target.items()):
1✔
2163
            if target_address is not None and target_address <= vm_code_start_address:
1✔
2164
                for code in codes:
1✔
2165
                    code.set_target(vm_code)
1✔
2166
                self._missing_target.pop(target_address)
1✔
2167

2168
    def _update_codes_without_target_to_next(self, address: int = None):
1✔
2169
        if address is None:
1✔
2170
            address = self.bytecode_size
×
2171

2172
        instance = VMCodeMapping.instance()
1✔
2173
        vm_code = instance.get_code(address)
1✔
2174
        if vm_code is None:
1✔
2175
            return
×
2176

2177
        next_address = instance.get_end_address(vm_code)
1✔
2178
        if address in self._missing_target:
1✔
2179
            targets = self._missing_target.pop(address)
×
2180

2181
            if next_address in self._missing_target:
×
2182
                self._missing_target[next_address].extend(targets)
×
2183
            else:
2184
                self._missing_target[next_address] = targets
×
2185

2186
        if None in self._missing_target:
1✔
2187
            for code in self._missing_target[None]:
1✔
2188
                code_address = instance.get_start_address(code)
1✔
2189
                target_address = Integer.from_bytes(code.raw_data) + code_address
1✔
2190
                if target_address == address and target_address != code_address:
1✔
2191
                    data = self._get_jump_data(code.info, next_address - code_address)
1✔
2192
                    instance.update_vm_code(code, code.info, data)
1✔
2193

2194
    def set_code_targets(self):
1✔
2195
        for target, vmcodes in self._missing_target.copy().items():
1✔
2196
            if target is None:
1✔
2197
                for code in vmcodes.copy():
1✔
2198
                    relative_address: int = Integer.from_bytes(code.raw_data, signed=True)
1✔
2199
                    code_address: int = VMCodeMapping.instance().get_start_address(code)
1✔
2200
                    absolute_address = code_address + relative_address
1✔
2201
                    code.set_target(VMCodeMapping.instance().get_code(absolute_address))
1✔
2202

2203
                    vmcodes.remove(code)
1✔
2204
            else:
2205
                for code in vmcodes.copy():
×
2206
                    code.set_target(VMCodeMapping.instance().get_code(target))
×
2207
                    vmcodes.remove(code)
×
2208

2209
            if len(vmcodes) == 0:
1✔
2210
                self._missing_target.pop(target)
1✔
2211

2212
    def _insert_jump(self, op_info: OpcodeInformation, jump_to: Union[int, VMCode] = 0, insert_jump: bool = False):
1✔
2213
        """
2214
        Inserts a jump opcode into the bytecode
2215

2216
        :param op_info: info of the opcode  that will be inserted
2217
        :param jump_to: data of the opcode
2218
        :param insert_jump: whether it should be included a jump to the end before the else branch
2219
        """
2220
        if isinstance(jump_to, VMCode):
1✔
2221
            jump_to = VMCodeMapping.instance().get_start_address(jump_to) - VMCodeMapping.instance().bytecode_size
1✔
2222

2223
        if self.last_code.opcode is not Opcode.RET or insert_jump:
1✔
2224
            data: bytes = self._get_jump_data(op_info, jump_to)
1✔
2225
            self.__insert1(op_info, data)
1✔
2226
        for x in range(op_info.stack_items):
1✔
2227
            self._stack_pop()
1✔
2228

2229
    def _update_jump(self, jump_address: int, updated_jump_to: int):
1✔
2230
        """
2231
        Updates the data of a jump code in the bytecode
2232

2233
        :param jump_address: jump code start address
2234
        :param updated_jump_to: new data of the code
2235
        """
2236
        vmcode: VMCode = VMCodeMapping.instance().get_code(jump_address)
1✔
2237
        if vmcode is not None:
1✔
2238
            if updated_jump_to in VMCodeMapping.instance().code_map:
1✔
2239
                self._remove_missing_target(vmcode)
1✔
2240
                target: VMCode = VMCodeMapping.instance().get_code(updated_jump_to)
1✔
2241
                vmcode.set_target(target)
1✔
2242
            else:
2243
                data: bytes = self._get_jump_data(vmcode.info, updated_jump_to - jump_address)
1✔
2244
                VMCodeMapping.instance().update_vm_code(vmcode, vmcode.info, data)
1✔
2245
                if updated_jump_to not in VMCodeMapping.instance().code_map:
1✔
2246
                    self._include_missing_target(vmcode, updated_jump_to)
1✔
2247

2248
    def change_jump(self, jump_address: int, new_jump_opcode: Opcode):
1✔
2249
        """
2250
        Changes the type of jump code in the bytecode
2251

2252
        """
2253
        if not OpcodeHelper.is_jump(new_jump_opcode):
1✔
2254
            return
×
2255

2256
        vmcode: VMCode = VMCodeMapping.instance().get_code(jump_address)
1✔
2257
        if vmcode is not None and OpcodeHelper.is_jump(vmcode.opcode):
1✔
2258
            previous_consumed_items_from_stack = vmcode.info.stack_items
1✔
2259
            new_consumed_items_from_stack = OpcodeInfo.get_info(new_jump_opcode).stack_items
1✔
2260
            if previous_consumed_items_from_stack < new_consumed_items_from_stack:
1✔
2261
                # if previous jump doesn't have condition and the new one has
2262
                previous_stack = self._stack_states.get_state(jump_address)
1✔
2263
                items_to_consume = new_consumed_items_from_stack - previous_consumed_items_from_stack
1✔
2264

2265
                if jump_address == self.last_code_start_address:
1✔
2266
                    for _ in range(items_to_consume):
1✔
2267
                        self._stack_pop()
1✔
2268
                    target_stack_size = len(self._stack)
1✔
2269
                else:
2270
                    target_stack_size = len(previous_stack) - items_to_consume
×
2271

2272
                while len(previous_stack) > target_stack_size:
1✔
2273
                    previous_stack.pop(-1)  # consume last stack item as jump condition
1✔
2274

2275
            vmcode.set_opcode(OpcodeInfo.get_info(new_jump_opcode))
1✔
2276

2277
    def _get_jump_data(self, op_info: OpcodeInformation, jump_to: int) -> bytes:
1✔
2278
        return Integer(jump_to).to_byte_array(min_length=op_info.data_len, signed=True)
1✔
2279

2280
    def _add_to_metadata_permissions(self, contract_class: ContractInterfaceClass, method_name: str):
1✔
2281
        from boa3.internal.compiler.compiledmetadata import CompiledMetadata
1✔
2282
        CompiledMetadata.instance().add_contract_permission(contract_class.contract_hash, method_name)
1✔
2283

2284
    def duplicate_stack_top_item(self):
1✔
2285
        self.duplicate_stack_item(1)
1✔
2286

2287
    def duplicate_stack_item(self, pos: int = 0, *, expected_stack_item: IType = None):
1✔
2288
        """
2289
        Duplicates the item n back in the stack
2290

2291
        :param pos: index of the variable
2292
        """
2293
        # n = 1 -> duplicates stack top item
2294
        # n = 0 -> value varies in runtime
2295
        if pos >= 0:
1✔
2296
            opcode: Opcode = OpcodeHelper.get_dup(pos)
1✔
2297
            if opcode is Opcode.PICK:
1✔
2298
                if pos > 0:
1✔
2299
                    self.convert_literal(pos - 1)
1✔
2300
                    self._stack_pop()
1✔
2301
                elif Type.int.is_type_of(self._stack[-1]) and expected_stack_item is not None:
1✔
2302
                    self._stack_pop()
1✔
2303

2304
            op_info = OpcodeInfo.get_info(opcode)
1✔
2305
            self.__insert1(op_info)
1✔
2306
            if expected_stack_item is None:
1✔
2307
                stacked_value = self._stack[-pos]
1✔
2308
            else:
2309
                stacked_value = expected_stack_item
1✔
2310

2311
            self._stack_append(stacked_value)
1✔
2312

2313
    def move_stack_item_to_top(self, pos: int = 0):
1✔
2314
        """
2315
        Moves the item n back in the stack to the top
2316

2317
        :param pos: index of the variable
2318
        """
2319
        # n = 1 -> stack top item
2320
        # n = 0 -> value varies in runtime
2321
        # do nothing in those cases
2322
        if pos == 2:
1✔
2323
            self.swap_reverse_stack_items(2)
×
2324
        elif pos > 2:
1✔
2325
            self.convert_literal(pos - 1)
1✔
2326
            self._stack_pop()
1✔
2327
            self.__insert1(OpcodeInfo.ROLL)
1✔
2328
            stack_type = self._stack_pop(-pos)
1✔
2329
            self._stack_append(stack_type)
1✔
2330

2331
    def clear_stack(self, clear_if_in_loop: bool = False):
1✔
2332
        if not clear_if_in_loop or len(self._current_for) > 0:
1✔
2333
            for _ in range(self.stack_size):
1✔
2334
                self.__insert1(OpcodeInfo.DROP)
1✔
2335

2336
    def remove_stack_top_item(self):
1✔
2337
        self.remove_stack_item(1)
1✔
2338

2339
    def remove_stack_item(self, pos: int = 0):
1✔
2340
        """
2341
        Removes the item n from the stack
2342

2343
        :param pos: index of the variable
2344
        """
2345
        # n = 1 -> removes stack top item
2346
        if pos > 0:
1✔
2347
            opcode: Opcode = OpcodeHelper.get_drop(pos)
1✔
2348
            if opcode is Opcode.XDROP:
1✔
2349
                self.convert_literal(pos - 1)
×
2350
                self._stack_pop()
×
2351
            op_info = OpcodeInfo.get_info(opcode)
1✔
2352
            self.__insert1(op_info)
1✔
2353
            if pos > 0 and len(self._stack) > 0:
1✔
2354
                self._stack_pop(-pos)
1✔
2355

2356
    def _remove_inserted_opcodes_since(self, last_address: int, last_stack_size: Optional[int] = None):
1✔
2357
        self._stack_states.restore_state(last_address)
1✔
2358
        if VMCodeMapping.instance().bytecode_size > last_address:
1✔
2359
            # remove opcodes inserted during the evaluation of the symbol
2360
            VMCodeMapping.instance().remove_opcodes(last_address, VMCodeMapping.instance().bytecode_size)
1✔
2361

2362
        if isinstance(last_stack_size, int) and last_stack_size < self.stack_size:
1✔
2363
            # remove any additional values pushed to the stack during the evalution of the symbol
2364
            for _ in range(self.stack_size - last_stack_size):
1✔
2365
                self._stack_pop()
1✔
2366

2367
    def swap_reverse_stack_items(self, no_items: int = 0, rotate: bool = False):
1✔
2368
        # n = 0 -> value varies in runtime
2369
        if 0 <= no_items != 1:
1✔
2370
            opcode: Opcode = OpcodeHelper.get_reverse(no_items, rotate)
1✔
2371
            if opcode is Opcode.REVERSEN and no_items > 0:
1✔
2372
                self.convert_literal(no_items)
1✔
2373
            op_info = OpcodeInfo.get_info(opcode)
1✔
2374
            self.__insert1(op_info)
1✔
2375
            if opcode is Opcode.REVERSEN and no_items > 0:
1✔
2376
                self._stack_pop()
1✔
2377
            if no_items > 0:
1✔
2378
                self._stack.reverse(-no_items, rotate=rotate)
1✔
2379

2380
    def convert_init_user_class(self, class_type: ClassType):
1✔
2381
        # TODO: refactor when instance variables are implemented
2382
        if isinstance(class_type, UserClass):
1✔
2383
            # create an none-filled array with the size of the instance variables
2384
            no_instance_variables = len(class_type.instance_variables)
1✔
2385
            self.convert_new_empty_array(no_instance_variables, class_type)
1✔
2386

2387
            self.__insert1(OpcodeInfo.UNPACK)  # unpack for array concatenation
1✔
2388
            value = self._stack_pop()
1✔
2389
            self._stack_append(Type.int)
1✔
2390
            self.remove_stack_top_item()
1✔
2391

2392
            # copy the array that stores the class variables from that class
2393
            self.convert_user_class(class_type, class_type.identifier)
1✔
2394

2395
            self.__insert1(OpcodeInfo.UNPACK)  # unpack for array concatenation
1✔
2396
            self._stack_append(value)
1✔
2397
            self.convert_literal(no_instance_variables)
1✔
2398
            self.convert_operation(BinaryOp.Add)
1✔
2399

2400
            self.__insert1(OpcodeInfo.PACK)  # packs everything together
1✔
2401
            self._stack_pop()
1✔
2402

2403
    def generate_implicit_init_user_class(self, init_method: Method):
1✔
2404
        if not init_method.is_called:
1✔
2405
            return
1✔
2406

2407
        self.convert_begin_method(init_method)
1✔
2408
        class_type = init_method.return_type
1✔
2409
        for base in class_type.bases:
1✔
2410
            base_constructor = base.constructor_method()
1✔
2411
            num_args = len(base_constructor.args)
1✔
2412

2413
            for arg_id, arg_var in reversed(list(init_method.args.items())):
1✔
2414
                self.convert_load_variable(arg_id, arg_var)
1✔
2415

2416
            args_to_call = num_args - 1 if num_args > 0 else num_args
1✔
2417
            self.convert_method_call(base_constructor, args_to_call)
1✔
2418
            self.remove_stack_top_item()
1✔
2419

2420
        self.convert_end_method()
1✔
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