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

CityOfZion / neo3-boa / 9a535731-11a4-476e-ac4c-f4b1ec2d8bb7

25 Sep 2023 08:49PM UTC coverage: 91.739% (+0.001%) from 91.738%
9a535731-11a4-476e-ac4c-f4b1ec2d8bb7

push

circleci

Mirella de Medeiros
CU-864ea8yf8 - Create functional tests for Neo 3.6 features

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

19967 of 21765 relevant lines covered (91.74%)

3.62 hits per line

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

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

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

44

45
class CodeGenerator:
4✔
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
4✔
53
    def generate_code(analyser: Analyser) -> CompilerOutput:
4✔
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()
4✔
61
        analyser.update_symbol_table_with_imports()
4✔
62

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

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

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

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

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

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

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

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

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

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

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

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

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

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

115
                visitor.global_stmts = static_stmts
4✔
116

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

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

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

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

137
        except CompilerError:
×
138
            pass
×
139

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

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

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

159
        return imports
4✔
160

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

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

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

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

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

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

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

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

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

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

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

201
    @property
4✔
202
    def output(self) -> CompilerOutput:
4✔
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)
4✔
209
        self.set_code_targets()
4✔
210
        VMCodeMapping.instance().remove_opcodes_by_code(opcodes)
4✔
211
        self._opcodes_to_remove.clear()
4✔
212
        return VMCodeMapping.instance().result()
4✔
213

214
    @property
4✔
215
    def last_code(self) -> Optional[VMCode]:
4✔
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:
4✔
223
            return VMCodeMapping.instance().codes[-1]
4✔
224
        else:
225
            return None
4✔
226

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

231
    @property
4✔
232
    def stack_size(self) -> int:
4✔
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)
4✔
239

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

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

246
    @property
4✔
247
    def last_code_start_address(self) -> int:
4✔
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()
4✔
254
        if len(instance.codes) > 0:
4✔
255
            return instance.get_start_address(instance.codes[-1])
4✔
256
        else:
257
            return 0
4✔
258

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

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

268
    @property
4✔
269
    def _args(self) -> List[str]:
4✔
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())
4✔
276

277
    @property
4✔
278
    def _locals(self) -> List[str]:
4✔
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())
4✔
285

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

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

294
    def _module_variables(self, modified_variable: bool) -> List[str]:
4✔
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:
4✔
301
            vars_map = self._global_vars
4✔
302
        else:
303
            vars_map = self._static_vars
4✔
304

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

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

321
        if not self.can_init_static_fields:
4✔
322
            for imported in self.symbol_table.values():
4✔
323
                if isinstance(imported, Import):
4✔
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():
4✔
326
                        if (isinstance(var, Variable)
4✔
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))
4✔
331
                            module_global_ids.append(var_id)
4✔
332
                            result_global_vars.append(var)
4✔
333

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

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

344
        original_ids = []
4✔
345
        for value in result:
4✔
346
            split = value.split(constants.VARIABLE_NAME_SEPARATOR)
4✔
347
            if len(split) > 1:
4✔
348
                new_index = split[-1]
4✔
349
            else:
350
                new_index = value
4✔
351
            original_ids.append(new_index)
4✔
352

353
        if vars_map != result_map:
4✔
354
            if vars_map is None:
4✔
355
                # save to keep the same order in future accesses
356
                if modified_variable:
4✔
357
                    self._global_vars = result_map
4✔
358
                else:
359
                    self._static_vars = result_map
4✔
360

361
            else:
362
                # reorder to keep the same order as the first access
363
                pre_reordered_ids = [var_id for (var_id, var) in vars_map]
4✔
364
                for index, (value, var) in enumerate(vars_map):
4✔
365
                    if value not in result:
4✔
366
                        if var in result_global_vars:
4✔
367
                            var_index = result_global_vars.index(var)
4✔
368
                            new_value = result_map[var_index]
4✔
369
                        else:
370
                            var_index = original_ids.index(value)
×
371
                            new_value = result_map[var_index]
×
372

373
                        vars_map[index] = new_value
4✔
374

375
                # add new symbols at the end always
376
                reordered_ids = [var_id for (var_id, var) in vars_map]
4✔
377
                additional_items = []
4✔
378
                for index, var_id in enumerate(result):
4✔
379
                    if var_id not in reordered_ids and var_id not in pre_reordered_ids:
4✔
380
                        additional_items.append(var_id)
4✔
381
                        vars_map.append(result_map[index])
4✔
382

383
                result = reordered_ids + additional_items
4✔
384

385
        return result
4✔
386

387
    @property
4✔
388
    def _current_scope(self) -> SymbolScope:
4✔
389
        return self._scope_stack[-1] if len(self._scope_stack) > 0 else self._global_scope
4✔
390

391
    def is_none_inserted(self) -> bool:
4✔
392
        """
393
        Checks whether the last insertion is null
394

395
        :return: whether the last value is null
396
        """
397
        return (self.last_code.opcode is Opcode.PUSHNULL or
4✔
398
                (len(self._stack) > 0 and self._stack[-1] is Type.none))
399

400
    def get_symbol(self, identifier: str, scope: Optional[ISymbol] = None, is_internal: bool = False) -> Tuple[str, ISymbol]:
4✔
401
        """
402
        Gets a symbol in the symbol table by its id
403

404
        :param identifier: id of the symbol
405
        :return: the symbol if exists. Symbol None otherwise
406
        """
407
        cur_symbol_table = self.symbol_table.copy()
4✔
408
        if isinstance(self.additional_symbols, dict):
4✔
409
            cur_symbol_table.update(self.additional_symbols)
×
410

411
        found_id = None
4✔
412
        found_symbol = None
4✔
413
        if len(self._scope_stack) > 0:
4✔
414
            for symbol_scope in self._scope_stack:
4✔
415
                if identifier in symbol_scope:
4✔
416
                    found_id, found_symbol = identifier, symbol_scope[identifier]
4✔
417
                    break
4✔
418

419
        if found_id is None:
4✔
420
            if scope is not None and hasattr(scope, 'symbols') and isinstance(scope.symbols, dict):
4✔
421
                if identifier in scope.symbols and isinstance(scope.symbols[identifier], ISymbol):
×
422
                    found_id, found_symbol = identifier, scope.symbols[identifier]
×
423
            else:
424
                if self._current_method is not None and identifier in self._current_method.symbols:
4✔
425
                    found_id, found_symbol = identifier, self._current_method.symbols[identifier]
4✔
426
                elif identifier in cur_symbol_table:
4✔
427
                    found_id, found_symbol = identifier, cur_symbol_table[identifier]
4✔
428
                else:
429
                    # the symbol may be a built-in. If not, returns None
430
                    symbol = Builtin.get_symbol(identifier)
4✔
431
                    if symbol is None:
4✔
432
                        symbol = Interop.get_symbol(identifier)
4✔
433

434
                    if symbol is not None:
4✔
435
                        found_id, found_symbol = identifier, symbol
4✔
436

437
                    elif not isinstance(identifier, str):
4✔
438
                        found_id, found_symbol = identifier, symbol
×
439

440
                    else:
441
                        split = identifier.split(constants.ATTRIBUTE_NAME_SEPARATOR)
4✔
442
                        if len(split) > 1:
4✔
443
                            attribute, symbol_id = constants.ATTRIBUTE_NAME_SEPARATOR.join(split[:-1]), split[-1]
4✔
444
                            another_attr_id, attr = self.get_symbol(attribute, is_internal=is_internal)
4✔
445
                            if hasattr(attr, 'symbols') and symbol_id in attr.symbols:
4✔
446
                                found_id, found_symbol = symbol_id, attr.symbols[symbol_id]
4✔
447
                            elif isinstance(attr, Package) and symbol_id in attr.inner_packages:
4✔
448
                                found_id, found_symbol = symbol_id, attr.inner_packages[symbol_id]
4✔
449

450
                        if found_id is None and is_internal:
4✔
451
                            from boa3.internal.model import imports
×
452
                            found_symbol = imports.builtin.get_internal_symbol(identifier)
×
453
                            if isinstance(found_symbol, ISymbol):
×
454
                                found_id = identifier
×
455

456
        if found_id is not None:
4✔
457
            if isinstance(found_symbol, Variable) and not found_symbol.is_reassigned and found_id not in self._statics:
4✔
458
                # verifies if it's a static variable with a unique name
459
                for static_id, static_var in self._static_vars:
4✔
460
                    if found_symbol == static_var:
4✔
461
                        found_id = static_id
×
462
                        break
×
463

464
            return found_id, found_symbol
4✔
465
        return identifier, Type.none
4✔
466

467
    def initialize_static_fields(self) -> bool:
4✔
468
        """
469
        Converts the signature of the method
470

471
        :return: whether there are static fields to be initialized
472
        """
473
        if not self.can_init_static_fields:
4✔
474
            return False
4✔
475
        if self.initialized_static_fields:
4✔
476
            return False
×
477

478
        num_static_fields = len(self._statics)
4✔
479
        if num_static_fields > 0:
4✔
480
            init_data = bytearray([num_static_fields])
4✔
481
            self.__insert1(OpcodeInfo.INITSSLOT, init_data)
4✔
482

483
            if constants.INITIALIZE_METHOD_ID in self.symbol_table:
4✔
484
                from boa3.internal.helpers import get_auxiliary_name
×
485
                method = self.symbol_table.pop(constants.INITIALIZE_METHOD_ID)
×
486
                new_id = get_auxiliary_name(constants.INITIALIZE_METHOD_ID, method)
×
487
                self.symbol_table[new_id] = method
×
488

489
            init_method = Method(is_public=True)
4✔
490
            init_method.init_bytecode = self.last_code
4✔
491
            self.symbol_table[constants.INITIALIZE_METHOD_ID] = init_method
4✔
492

493
        return num_static_fields > 0
4✔
494

495
    def end_initialize(self):
4✔
496
        """
497
        Converts the signature of the method
498
        """
499
        self.insert_return()
4✔
500
        self.initialized_static_fields = True
4✔
501

502
        if constants.INITIALIZE_METHOD_ID in self.symbol_table:
4✔
503
            init_method = self.symbol_table[constants.INITIALIZE_METHOD_ID]
4✔
504
            init_method.end_bytecode = self.last_code
4✔
505

506
    def convert_begin_method(self, method: Method):
4✔
507
        """
508
        Converts the signature of the method
509

510
        :param method: method that is being converted
511
        """
512
        new_variable_scope = self._scope_stack[-1].copy() if len(self._scope_stack) > 0 else SymbolScope()
4✔
513
        self._scope_stack.append(new_variable_scope)
4✔
514

515
        num_args: int = len(method.args)
4✔
516
        num_vars: int = len(method.locals)
4✔
517

518
        method.init_address = VMCodeMapping.instance().bytecode_size
4✔
519
        if num_args > 0 or num_vars > 0:
4✔
520
            init_data = bytearray([num_vars, num_args])
4✔
521
            self.__insert1(OpcodeInfo.INITSLOT, init_data)
4✔
522
            method.init_bytecode = self.last_code
4✔
523
        self._current_method = method
4✔
524

525
    def convert_end_method(self, method_id: Optional[str] = None):
4✔
526
        """
527
        Converts the end of the method
528
        """
529
        if (self._current_method.init_bytecode is None
4✔
530
                and self._current_method.init_address in VMCodeMapping.instance().code_map):
531
            self._current_method.init_bytecode = VMCodeMapping.instance().code_map[self._current_method.init_address]
4✔
532

533
        if self.last_code.opcode is not Opcode.RET or self._check_codes_with_target():
4✔
534
            if self._current_method.is_init:
4✔
535
                # return the built object if it's a constructor
536
                self_id, self_value = list(self._current_method.args.items())[0]
4✔
537
                self.convert_load_variable(self_id, self_value)
4✔
538

539
            self.insert_return()
4✔
540

541
        self._current_method.end_bytecode = self.last_code
4✔
542
        self._current_method = None
4✔
543
        self._stack.clear()
4✔
544

545
        function_variable_scope = self._scope_stack.pop()
4✔
546

547
    def insert_return(self):
4✔
548
        """
549
        Insert the return statement
550
        """
551
        self.__insert1(OpcodeInfo.RET)
4✔
552

553
    def insert_not(self):
4✔
554
        """
555
        Insert a `not` to change the value of a bool
556
        """
557
        self.__insert1(OpcodeInfo.NOT)
×
558

559
    def insert_nop(self):
4✔
560
        """
561
        Insert a NOP opcode
562
        """
563
        self.__insert1(OpcodeInfo.NOP)
4✔
564

565
    def insert_sys_call(self, sys_call_id: bytes):
4✔
566
        """
567
        Insert a SYSCALL opcode call
568
        """
569
        self.__insert1(OpcodeInfo.SYSCALL, sys_call_id)
4✔
570

571
    def convert_begin_while(self, is_for: bool = False) -> int:
4✔
572
        """
573
        Converts the beginning of the while statement
574

575
        :param is_for: whether the loop is a for loop or not
576
        :return: the address of the while first opcode
577
        """
578
        # it will be updated when the while ends
579
        self._insert_jump(OpcodeInfo.JMP)
4✔
580

581
        start_address = self.last_code_start_address
4✔
582
        self._current_loop.append(start_address)
4✔
583
        if is_for:
4✔
584
            self._current_for.append(start_address)
4✔
585

586
        return start_address
4✔
587

588
    def convert_end_while(self, start_address: int, test_address: int, *, is_internal: bool = False) -> int:
4✔
589
        """
590
        Converts the end of the while statement
591

592
        :param start_address: the address of the while first opcode
593
        :param test_address: the address of the while test fist opcode
594
        :param is_internal: whether it was called when generating other implemented symbols
595
        """
596
        return self.convert_end_loop(start_address, test_address, False, is_internal=is_internal)
4✔
597

598
    def convert_begin_for(self) -> int:
4✔
599
        """
600
        Converts the beginning of the for statement
601

602
        :return: the address of the for first opcode
603
        """
604
        self.convert_literal(0)
4✔
605
        address = self.convert_begin_while(True)
4✔
606

607
        self.duplicate_stack_item(2)  # duplicate for sequence
4✔
608
        self.duplicate_stack_item(2)  # duplicate for index
4✔
609
        self.convert_get_item()
4✔
610
        return address
4✔
611

612
    def convert_end_for(self, start_address: int, is_internal: bool = False) -> int:
4✔
613
        """
614
        Converts the end of the for statement
615

616
        :param start_address: the address of the for first opcode
617
        :param is_internal: whether it was called when generating other implemented symbols
618
        :return: the address of the loop condition
619
        """
620
        self.__insert1(OpcodeInfo.INC)      # index += 1
4✔
621
        if len(self._stack) < 1 or self._stack[-1] is not Type.int:
4✔
622
            self._stack_append(Type.int)
4✔
623
        for_increment = self.last_code_start_address
4✔
624
        test_address = VMCodeMapping.instance().bytecode_size
4✔
625
        self._update_continue_jumps(start_address, for_increment)
4✔
626

627
        self.duplicate_stack_top_item()     # dup index and sequence
4✔
628
        self.duplicate_stack_item(3)
4✔
629
        self.convert_builtin_method_call(Builtin.Len)
4✔
630
        self.convert_operation(BinaryOp.Lt)  # continue loop condition: index < len(sequence)
4✔
631

632
        self.convert_end_loop(start_address, test_address, True, is_internal=is_internal)
4✔
633

634
        return test_address
4✔
635

636
    def convert_end_loop(self, start_address: int, test_address: int, is_for: bool, is_internal: bool = False) -> int:
4✔
637
        """
638
        Converts the end of a loop statement
639

640
        :param start_address: the address of the while first opcode
641
        :param test_address: the address of the while test fist opcode
642
        :param is_for: whether the loop is a for loop or not
643
        :param is_internal: whether it was called when generating other implemented symbols
644
        """
645
        # updates the begin jmp with the target address
646
        self._update_jump(start_address, test_address)
4✔
647
        self._update_continue_jumps(start_address, test_address)
4✔
648

649
        # inserts end jmp
650
        while_begin: VMCode = VMCodeMapping.instance().code_map[start_address]
4✔
651
        while_body: int = VMCodeMapping.instance().get_end_address(while_begin) + 1
4✔
652
        end_jmp_to: int = while_body - VMCodeMapping.instance().bytecode_size
4✔
653
        self._insert_jump(OpcodeInfo.JMPIF, end_jmp_to)
4✔
654

655
        self._current_loop.pop()
4✔
656

657
        is_break_pos = self.bytecode_size
4✔
658
        self.convert_literal(False)  # is not break
4✔
659
        is_break_end = self.last_code_start_address
4✔
660
        self._update_break_jumps(start_address)
4✔
661

662
        if is_for:
4✔
663
            self._current_for.pop()
4✔
664
            reverse_to_drop_pos = self.last_code_start_address
4✔
665
            self.swap_reverse_stack_items(3)
4✔
666
            reverse_to_drop_end = self.last_code_start_address
4✔
667

668
            self.remove_stack_top_item()    # removes index and sequence from stack
4✔
669
            self.remove_stack_top_item()
4✔
670

671
            self._insert_loop_break_addresses(start_address, reverse_to_drop_pos, reverse_to_drop_end, self.bytecode_size)
4✔
672

673
        last_opcode = self.bytecode_size
4✔
674
        self._insert_loop_break_addresses(start_address, is_break_pos, is_break_end, last_opcode)
4✔
675
        self._insert_jump(OpcodeInfo.JMPIF)
4✔
676

677
        if is_internal:
4✔
678
            self.convert_end_loop_else(start_address, self.last_code_start_address)
4✔
679
        return last_opcode
4✔
680

681
    def convert_end_loop_else(self, start_address: int, else_begin: int, has_else: bool = False, is_for: bool = False):
4✔
682
        """
683
        Updates the break loops jumps
684

685
        :param start_address: the address of the loop first opcode
686
        :param else_begin: the address of the else first opcode. Equals to code size if has_else is False
687
        :param has_else: whether this loop has an else branch
688
        :param is_for: whether the loop is a for loop or not
689
        """
690
        if start_address in self._jumps_to_loop_break:
4✔
691
            is_loop_insertions = []
4✔
692
            if start_address in self._inserted_loop_breaks:
4✔
693
                is_loop_insertions = self._inserted_loop_breaks.pop(start_address)
4✔
694
            is_loop_insertions.append(else_begin)
4✔
695

696
            if not has_else:
4✔
697
                self._opcodes_to_remove.extend(is_loop_insertions)
4✔
698
            else:
699
                min_break_addresses = 4 if is_for else 3
4✔
700
                if (start_address in self._jumps_to_loop_break
4✔
701
                        and len(self._jumps_to_loop_break[start_address]) < 2
702
                        and len(is_loop_insertions) < min_break_addresses):
703
                    # if len is less than 2, it means it has no breaks or the only break is else branch begin
704
                    # so it can remove the jump in the beginning of else branch
705
                    self._opcodes_to_remove.extend(is_loop_insertions)
4✔
706
                self._update_jump(else_begin, VMCodeMapping.instance().bytecode_size)
4✔
707

708
    def convert_begin_if(self) -> int:
4✔
709
        """
710
        Converts the beginning of the if statement
711

712
        :return: the address of the if first opcode
713
        """
714
        # it will be updated when the if ends
715
        self._insert_jump(OpcodeInfo.JMPIFNOT)
4✔
716
        return VMCodeMapping.instance().get_start_address(self.last_code)
4✔
717

718
    def convert_begin_else(self, start_address: int, insert_jump: bool = False, is_internal: bool = False) -> int:
4✔
719
        """
720
        Converts the beginning of the if else statement
721

722
        :param start_address: the address of the if first opcode
723
        :param insert_jump: whether it should be included a jump to the end before the else branch
724
        :return: the address of the if else first opcode
725
        """
726
        # it will be updated when the if ends
727
        self._insert_jump(OpcodeInfo.JMP, insert_jump=insert_jump)
4✔
728

729
        # updates the begin jmp with the target address
730
        self._update_jump(start_address, VMCodeMapping.instance().bytecode_size)
4✔
731
        if is_internal:
4✔
732
            self._stack_states.restore_state(start_address + 1)
4✔
733

734
        return self.last_code_start_address
4✔
735

736
    def convert_end_if(self, start_address: int, is_internal: bool = False):
4✔
737
        """
738
        Converts the end of the if statement
739

740
        :param start_address: the address of the if first opcode
741
        """
742
        # updates the begin jmp with the target address
743
        self._update_jump(start_address, VMCodeMapping.instance().bytecode_size)
4✔
744
        if is_internal:
4✔
745
            self._stack_states.restore_state(start_address)
4✔
746

747
    def convert_begin_try(self) -> int:
4✔
748
        """
749
        Converts the beginning of the try statement
750

751
        :return: the address of the try first opcode
752
        """
753
        # it will be updated when the while ends
754
        self.__insert_code(TryCode())
4✔
755

756
        return self.last_code_start_address
4✔
757

758
    def convert_try_except(self, exception_id: Optional[str]) -> int:
4✔
759
        """
760
        Converts the end of the try statement
761

762
        :param exception_id: the name identifier of the exception
763
        :type exception_id: str or None
764

765
        :return: the last address from try body
766
        """
767
        self._insert_jump(OpcodeInfo.JMP)
4✔
768
        last_try_code = self.last_code_start_address
4✔
769

770
        self._stack_append(Type.exception)  # when reaching the except body, an exception was raised
4✔
771
        if exception_id is None:
4✔
772
            self.remove_stack_top_item()
4✔
773

774
        return last_try_code
4✔
775

776
    def convert_end_try(self, start_address: int,
4✔
777
                        end_address: Optional[int] = None,
778
                        else_address: Optional[int] = None) -> int:
779
        """
780
        Converts the end of the try statement
781

782
        :param start_address: the address of the try first opcode
783
        :param end_address: the address of the try last opcode. If it is None, there's no except body.
784
        :param else_address: the address of the try else. If it is None, there's no else body.
785
        :return: the last address of the except body
786
        """
787
        self.__insert1(OpcodeInfo.ENDTRY)
4✔
788
        if end_address is not None:
4✔
789
            vmcode_mapping_instance = VMCodeMapping.instance()
4✔
790

791
            try_vm_code = vmcode_mapping_instance.get_code(start_address)
4✔
792
            try_jump = vmcode_mapping_instance.get_code(end_address)
4✔
793

794
            except_start_address = vmcode_mapping_instance.get_end_address(try_jump) + 1
4✔
795
            except_start_code = vmcode_mapping_instance.get_code(except_start_address)
4✔
796

797
            if isinstance(try_vm_code, TryCode):
4✔
798
                try_vm_code.set_except_code(except_start_code)
4✔
799
            self._update_jump(else_address if else_address is not None else end_address, self.last_code_start_address)
4✔
800

801
        return self.last_code_start_address
4✔
802

803
    def convert_end_try_finally(self, last_address: int, start_address: int, has_try_body: bool = False):
4✔
804
        """
805
        Converts the end of the try finally statement
806

807
        :param last_address: the address of the try except last opcode.
808
        :param start_address: the address of the try first opcode
809
        :param has_try_body: whether this try statement has a finally body.
810
        :return: the last address of the except body
811
        """
812
        if has_try_body:
4✔
813
            self.__insert1(OpcodeInfo.ENDFINALLY)
4✔
814
            vmcode_mapping_instance = VMCodeMapping.instance()
4✔
815

816
            try_vm_code = vmcode_mapping_instance.get_code(start_address)
4✔
817
            try_last_code = vmcode_mapping_instance.get_code(last_address)
4✔
818

819
            finally_start_address = vmcode_mapping_instance.get_end_address(try_last_code) + 1
4✔
820
            finally_start_code = vmcode_mapping_instance.get_code(finally_start_address)
4✔
821

822
            if isinstance(try_vm_code, TryCode):
4✔
823
                try_vm_code.set_finally_code(finally_start_code)
4✔
824
            self._update_jump(vmcode_mapping_instance.bytecode_size, self.last_code_start_address)
4✔
825

826
        self._update_jump(last_address, VMCodeMapping.instance().bytecode_size)
4✔
827

828
    def fix_negative_index(self, value_index: int = None, test_is_negative=True):
4✔
829
        self._can_append_target = not self._can_append_target
4✔
830

831
        value_code = self.last_code_start_address
4✔
832
        size = VMCodeMapping.instance().bytecode_size
4✔
833

834
        if test_is_negative:
4✔
835
            self.duplicate_stack_top_item()
4✔
836
            self.__insert1(OpcodeInfo.SIGN)
4✔
837
            self.convert_literal(-1)
4✔
838

839
            jmp_address = VMCodeMapping.instance().bytecode_size
4✔
840
            self._insert_jump(OpcodeInfo.JMPNE)     # if index < 0
4✔
841

842
        state = self._stack_states.get_state(value_index) if isinstance(value_index, int) else self._stack
4✔
843
        # get position of collection relative to top
844
        index_of_last = -1
4✔
845
        for index, value in reversed(list(enumerate(state))):
4✔
846
            if isinstance(value, ICollectionType):
4✔
847
                index_of_last = index
4✔
848
                break
4✔
849

850
        if index_of_last >= 0:
4✔
851
            pos_from_top = len(state) - index_of_last
4✔
852
        else:
853
            pos_from_top = 2
4✔
854

855
        self.duplicate_stack_item(pos_from_top)     # index += len(array)
4✔
856
        self.convert_builtin_method_call(Builtin.Len)
4✔
857
        self.convert_operation(BinaryOp.Add)
4✔
858

859
        if test_is_negative:
4✔
860
            if not isinstance(value_index, int):
4✔
861
                value_index = VMCodeMapping.instance().bytecode_size
4✔
862
            jmp_target = value_index if value_index < size else VMCodeMapping.instance().bytecode_size
4✔
863
            self._update_jump(jmp_address, jmp_target)
4✔
864

865
            VMCodeMapping.instance().move_to_end(value_index, value_code)
4✔
866

867
        self._can_append_target = not self._can_append_target
4✔
868

869
    def fix_index_out_of_range(self, has_another_index_in_stack: bool):
4✔
870
        """
871
        Will fix a negative index to 0 or an index greater than the sequence length to the length.
872

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

875
        :param has_another_index_in_stack: whether the stack is [..., Sequence, index, index] or [..., Sequence, index].
876
        """
877
        # if index is still negative, then it should be 0
878
        self.duplicate_stack_item(2 if has_another_index_in_stack else 1)
4✔
879
        self.__insert1(OpcodeInfo.SIGN)
4✔
880
        self.convert_literal(-1)
4✔
881
        jmp_address = VMCodeMapping.instance().bytecode_size
4✔
882
        self._insert_jump(OpcodeInfo.JMPNE)  # if index < 0, then index = 0
4✔
883

884
        if has_another_index_in_stack:
4✔
885
            self.swap_reverse_stack_items(2)
4✔
886
        self.remove_stack_top_item()
4✔
887
        self.convert_literal(0)
4✔
888
        if has_another_index_in_stack:
4✔
889
            self.swap_reverse_stack_items(2)
4✔
890
        jmp_target = VMCodeMapping.instance().bytecode_size
4✔
891
        self._update_jump(jmp_address, jmp_target)
4✔
892

893
        # index can not be greater than len(string)
894
        self.duplicate_stack_item(3 if has_another_index_in_stack else 2)
4✔
895
        self.convert_builtin_method_call(Builtin.Len)
4✔
896
        self.__insert1(
4✔
897
            OpcodeInfo.MIN)  # the builtin MinMethod accepts more than 2 arguments, that's why this Opcode is being directly inserted
898
        self._stack_pop()
4✔
899

900
    def fix_index_negative_stride(self):
4✔
901
        """
902
        If stride is negative, then the array was reversed, thus, lower and upper should be changed
903
        accordingly.
904
        The Opcodes below will only fix 1 index.
905
        array[lower:upper:-1] == reversed_array[len(array)-lower-1:len(array)-upper-1]
906
        If lower or upper was None, then it's not necessary to change its values.
907
        """
908
        # top array should be: len(array), index
909
        self.convert_builtin_method_call(Builtin.Len)
4✔
910
        self.swap_reverse_stack_items(2)
4✔
911
        self.convert_operation(BinaryOp.Sub)
4✔
912
        self.__insert1(OpcodeInfo.DEC)
4✔
913

914
    def convert_loop_continue(self):
4✔
915
        loop_start = self._current_loop[-1]
4✔
916
        self._insert_jump(OpcodeInfo.JMP)
4✔
917
        continue_address = self.last_code_start_address
4✔
918

919
        if loop_start not in self._jumps_to_loop_condition:
4✔
920
            self._jumps_to_loop_condition[loop_start] = [continue_address]
4✔
921
        else:
922
            self._jumps_to_loop_condition[loop_start].append(continue_address)
×
923

924
    def _update_continue_jumps(self, loop_start_address, loop_test_address):
4✔
925
        if loop_start_address in self._jumps_to_loop_condition:
4✔
926
            jump_addresses = self._jumps_to_loop_condition.pop(loop_start_address)
4✔
927
            for address in jump_addresses:
4✔
928
                self._update_jump(address, loop_test_address)
4✔
929

930
    def convert_loop_break(self):
4✔
931
        loop_start = self._current_loop[-1]
4✔
932
        is_break_pos = self.bytecode_size
4✔
933
        self.convert_literal(True)  # is break
4✔
934
        self._stack_pop()
4✔
935
        is_break_end = self.last_code_start_address
4✔
936
        self._insert_jump(OpcodeInfo.JMP)
4✔
937
        break_address = self.last_code_start_address
4✔
938

939
        self._insert_loop_break_addresses(loop_start, is_break_pos, is_break_end, break_address)
4✔
940

941
    def _insert_loop_break_addresses(self, loop_start: int, is_break_start: int, is_break_end: int, break_address: int):
4✔
942
        if loop_start not in self._jumps_to_loop_break:
4✔
943
            self._jumps_to_loop_break[loop_start] = [break_address]
4✔
944
        elif break_address not in self._jumps_to_loop_break[loop_start]:
4✔
945
            self._jumps_to_loop_break[loop_start].append(break_address)
4✔
946

947
        is_break_instructions = VMCodeMapping.instance().get_addresses(is_break_start, is_break_end)
4✔
948

949
        if loop_start not in self._inserted_loop_breaks:
4✔
950
            self._inserted_loop_breaks[loop_start] = is_break_instructions
4✔
951
        else:
952
            loop_breaks_list = self._inserted_loop_breaks[loop_start]
4✔
953
            for address in is_break_instructions:
4✔
954
                # don't include duplicated addresses
955
                if address not in loop_breaks_list:
4✔
956
                    loop_breaks_list.append(address)
4✔
957

958
    def _update_break_jumps(self, loop_start_address) -> int:
4✔
959
        jump_target = VMCodeMapping.instance().bytecode_size
4✔
960

961
        if loop_start_address in self._jumps_to_loop_break:
4✔
962
            jump_addresses = self._jumps_to_loop_break.pop(loop_start_address)
4✔
963
            for address in jump_addresses:
4✔
964
                self._update_jump(address, jump_target)
4✔
965

966
    def convert_literal(self, value: Any) -> int:
4✔
967
        """
968
        Converts a literal value
969

970
        :param value: the value to be converted
971
        :return: the converted value's start address in the bytecode
972
        """
973
        start_address = VMCodeMapping.instance().bytecode_size
4✔
974
        if isinstance(value, bool):
4✔
975
            self.convert_bool_literal(value)
4✔
976
        elif isinstance(value, int):
4✔
977
            self.convert_integer_literal(value)
4✔
978
        elif isinstance(value, str):
4✔
979
            self.convert_string_literal(value)
4✔
980
        elif value is None:
4✔
981
            self.insert_none()
4✔
982
        elif isinstance(value, (bytes, bytearray)):
4✔
983
            self.convert_byte_array(value)
4✔
984
        elif isinstance(value, Sequence):
4✔
985
            self.convert_sequence_literal(value)
4✔
986
        elif isinstance(value, dict):
4✔
987
            self.convert_dict_literal(value)
4✔
988
        else:
989
            # it's not a type that is supported by neo-boa
990
            raise NotImplementedError
×
991
        return start_address
4✔
992

993
    def convert_integer_literal(self, value: int):
4✔
994
        """
995
        Converts an integer literal value
996

997
        :param value: the value to be converted
998
        """
999
        opcode = OpcodeHelper.get_literal_push(value)
4✔
1000
        if opcode is not None:
4✔
1001
            op_info: OpcodeInformation = OpcodeInfo.get_info(opcode)
4✔
1002
            self.__insert1(op_info)
4✔
1003
            self._stack_append(Type.int)
4✔
1004
        else:
1005
            opcode = OpcodeHelper.get_literal_push(-value)
4✔
1006
            if opcode is not None:
4✔
1007
                op_info: OpcodeInformation = OpcodeInfo.get_info(opcode)
4✔
1008
                self.__insert1(op_info)
4✔
1009
                self._stack_append(Type.int)
4✔
1010
                self.convert_operation(UnaryOp.Negative)
4✔
1011
            else:
1012
                opcode, data = OpcodeHelper.get_push_and_data(value)
4✔
1013
                op_info: OpcodeInformation = OpcodeInfo.get_info(opcode)
4✔
1014
                self.__insert1(op_info, data)
4✔
1015
                self._stack_append(Type.int)
4✔
1016

1017
    def convert_string_literal(self, value: str):
4✔
1018
        """
1019
        Converts an string literal value
1020

1021
        :param value: the value to be converted
1022
        """
1023
        array = bytes(value, constants.ENCODING)
4✔
1024
        self.insert_push_data(array)
4✔
1025
        self.convert_cast(Type.str)
4✔
1026

1027
    def convert_bool_literal(self, value: bool):
4✔
1028
        """
1029
        Converts an boolean literal value
1030

1031
        :param value: the value to be converted
1032
        """
1033
        if value:
4✔
1034
            self.__insert1(OpcodeInfo.PUSHT)
4✔
1035
        else:
1036
            self.__insert1(OpcodeInfo.PUSHF)
4✔
1037
        self._stack_append(Type.bool)
4✔
1038

1039
    def convert_sequence_literal(self, sequence: Sequence):
4✔
1040
        """
1041
        Converts a sequence value
1042

1043
        :param sequence: the value to be converted
1044
        """
1045
        if isinstance(sequence, tuple):
4✔
1046
            value_type = Type.tuple.build(sequence)
4✔
1047
        else:
1048
            value_type = Type.list.build(list(sequence))
4✔
1049

1050
        for inner_value in reversed(sequence):
4✔
1051
            self.convert_literal(inner_value)
4✔
1052

1053
        self.convert_new_array(len(sequence), value_type)
4✔
1054

1055
    def convert_dict_literal(self, dictionary: dict):
4✔
1056
        """
1057
        Converts a dict value
1058

1059
        :param dictionary: the value to be converted
1060
        """
1061
        value_type = Type.dict.build(dictionary)
4✔
1062
        self.convert_new_map(value_type)
4✔
1063

1064
        for key, value in dictionary.items():
4✔
1065
            self.duplicate_stack_top_item()
4✔
1066
            self.convert_literal(key)
4✔
1067
            value_start = self.convert_literal(value)
4✔
1068
            self.convert_set_item(value_start)
4✔
1069

1070
    def convert_byte_array(self, array: bytes):
4✔
1071
        """
1072
        Converts a byte value
1073

1074
        :param array: the value to be converted
1075
        """
1076
        self.insert_push_data(array)
4✔
1077
        self.convert_cast(Type.bytearray if isinstance(array, bytearray)
4✔
1078
                          else Type.bytes)
1079

1080
    def insert_push_data(self, data: bytes):
4✔
1081
        """
1082
        Inserts a push data value
1083

1084
        :param data: the value to be converted
1085
        """
1086
        data_len: int = len(data)
4✔
1087
        if data_len <= OpcodeInfo.PUSHDATA1.max_data_len:
4✔
1088
            op_info = OpcodeInfo.PUSHDATA1
4✔
1089
        elif data_len <= OpcodeInfo.PUSHDATA2.max_data_len:
×
1090
            op_info = OpcodeInfo.PUSHDATA2
×
1091
        else:
1092
            op_info = OpcodeInfo.PUSHDATA4
×
1093

1094
        data = Integer(data_len).to_byte_array(min_length=op_info.data_len) + data
4✔
1095
        self.__insert1(op_info, data)
4✔
1096
        self._stack_append(Type.str)  # push data pushes a ByteString value in the stack
4✔
1097

1098
    def insert_none(self):
4✔
1099
        """
1100
        Converts None literal
1101
        """
1102
        self.__insert1(OpcodeInfo.PUSHNULL)
4✔
1103
        self._stack_append(Type.none)
4✔
1104

1105
    def convert_cast(self, value_type: IType, is_internal: bool = False):
4✔
1106
        """
1107
        Converts casting types in Neo VM
1108
        """
1109
        stack_top_type: IType = self._stack[-1]
4✔
1110
        if (not value_type.is_generic
4✔
1111
                and not stack_top_type.is_generic
1112
                and value_type.stack_item is not Type.any.stack_item):
1113

1114
            if is_internal or value_type.stack_item != stack_top_type.stack_item:
4✔
1115
                # converts only if the stack types are different
1116
                self.__insert1(OpcodeInfo.CONVERT, value_type.stack_item)
4✔
1117

1118
            # but changes the value internally
1119
            self._stack_pop()
4✔
1120
            self._stack_append(value_type)
4✔
1121

1122
    def convert_new_map(self, map_type: IType):
4✔
1123
        """
1124
        Converts the creation of a new map
1125

1126
        :param map_type: the Neo Boa type of the map
1127
        """
1128
        self.__insert1(OpcodeInfo.NEWMAP)
4✔
1129
        self._stack_append(map_type)
4✔
1130

1131
    def convert_new_empty_array(self, length: int, array_type: IType):
4✔
1132
        """
1133
        Converts the creation of a new empty array
1134

1135
        :param length: the size of the new array
1136
        :param array_type: the Neo Boa type of the array
1137
        """
1138
        if length <= 0:
4✔
1139
            self.__insert1(OpcodeInfo.NEWARRAY0)
4✔
1140
        else:
1141
            self.convert_literal(length)
4✔
1142
            self._stack_pop()
4✔
1143
            self.__insert1(OpcodeInfo.NEWARRAY)
4✔
1144
        self._stack_append(array_type)
4✔
1145

1146
    def convert_new_array(self, length: int, array_type: IType = Type.list):
4✔
1147
        """
1148
        Converts the creation of a new array
1149

1150
        :param length: the size of the new array
1151
        :param array_type: the Neo Boa type of the array
1152
        """
1153
        if length <= 0:
4✔
1154
            self.convert_new_empty_array(length, array_type)
4✔
1155
        else:
1156
            self.convert_literal(length)
4✔
1157
            if array_type.stack_item is StackItemType.Struct:
4✔
1158
                self.__insert1(OpcodeInfo.PACKSTRUCT)
×
1159
            else:
1160
                self.__insert1(OpcodeInfo.PACK)
4✔
1161
            self._stack_pop()  # array size
4✔
1162
            for x in range(length):
4✔
1163
                self._stack_pop()
4✔
1164
            self._stack_append(array_type)
4✔
1165

1166
    def _set_array_item(self, value_start_address: int, check_for_negative_index: bool = True):
4✔
1167
        """
1168
        Converts the end of setting af a value in an array
1169
        """
1170
        index_type: IType = self._stack[-2]  # top: index
4✔
1171
        if index_type is Type.int and check_for_negative_index:
4✔
1172
            self.fix_negative_index(value_start_address)
4✔
1173

1174
    def convert_set_item(self, value_start_address: int, index_inserted_internally: bool = False):
4✔
1175
        """
1176
        Converts the end of setting af a value in an array
1177
        """
1178
        item_type: IType = self._stack[-3]  # top: index, 2nd-to-top: value, 3nd-to-top: array or map
4✔
1179
        if item_type.stack_item is not StackItemType.Map:
4✔
1180
            self._set_array_item(value_start_address, check_for_negative_index=not index_inserted_internally)
4✔
1181

1182
        self.__insert1(OpcodeInfo.SETITEM)
4✔
1183
        self._stack_pop()  # value
4✔
1184
        self._stack_pop()  # index
4✔
1185
        self._stack_pop()  # array or map
4✔
1186

1187
    def _get_array_item(self, check_for_negative_index: bool = True, test_is_negative_index=True):
4✔
1188
        """
1189
        Converts the end of get a value in an array
1190
        """
1191
        index_type: IType = self._stack[-1]  # top: index
4✔
1192
        if index_type is Type.int and check_for_negative_index:
4✔
1193
            self.fix_negative_index(test_is_negative=test_is_negative_index)
4✔
1194

1195
    def convert_get_item(self, index_inserted_internally: bool = False, index_is_positive=False, test_is_negative_index=True):
4✔
1196
        array_or_map_type: IType = self._stack[-2]  # second-to-top: array or map
4✔
1197
        if array_or_map_type.stack_item is not StackItemType.Map:
4✔
1198
            self._get_array_item(check_for_negative_index=not (index_inserted_internally or index_is_positive),
4✔
1199
                                 test_is_negative_index=test_is_negative_index)
1200

1201
        if array_or_map_type is Type.str:
4✔
1202
            self.convert_literal(1)  # length of substring
4✔
1203
            self.convert_get_substring(is_internal=index_inserted_internally or index_is_positive or not test_is_negative_index)
4✔
1204
        else:
1205
            self.__insert1(OpcodeInfo.PICKITEM)
4✔
1206
            self._stack_pop()
4✔
1207
            self._stack_pop()
4✔
1208
            if hasattr(array_or_map_type, 'value_type'):
4✔
1209
                new_stack_item = array_or_map_type.value_type
4✔
1210
            else:
1211
                new_stack_item = Type.any
4✔
1212
            self._stack_append(new_stack_item)
4✔
1213

1214
    def convert_get_substring(self, *, is_internal: bool = False, fix_result_type: bool = True):
4✔
1215
        """
1216
        Converts the end of get a substring
1217

1218
        :param is_internal: whether it was called when generating other implemented symbols
1219
        """
1220
        if not is_internal:
4✔
1221
            # if given substring size is negative, return empty string
1222
            self.duplicate_stack_top_item()
4✔
1223
            self.convert_literal(0)
4✔
1224
            self.convert_operation(BinaryOp.GtE)
4✔
1225

1226
            self._insert_jump(OpcodeInfo.JMPIF)
4✔
1227
            jmp_address = self.last_code_start_address
4✔
1228
            self.remove_stack_top_item()
4✔
1229
            self.convert_literal(0)
4✔
1230

1231
        self._stack_pop()  # length
4✔
1232
        self._stack_pop()  # start
4✔
1233
        original = self._stack_pop()  # original string
4✔
1234

1235
        self.__insert1(OpcodeInfo.SUBSTR)
4✔
1236
        if not is_internal:
4✔
1237
            self._update_jump(jmp_address, self.last_code_start_address)
4✔
1238
        self._stack_append(BufferType)  # substr returns a buffer instead of a bytestring
4✔
1239
        if fix_result_type:
4✔
1240
            self.convert_cast(original)
4✔
1241

1242
    def convert_get_array_slice(self, array: SequenceType):
4✔
1243
        """
1244
        Converts the end of get a substring
1245
        """
1246
        self.convert_new_empty_array(0, array)      # slice = []
4✔
1247
        self.duplicate_stack_item(3)                # index = slice_start
4✔
1248

1249
        start_jump = self.convert_begin_while()  # while index < slice_end
4✔
1250
        self.duplicate_stack_top_item()             # if index >= slice_start
4✔
1251
        self.duplicate_stack_item(5)
4✔
1252
        self.convert_operation(BinaryOp.GtE)
4✔
1253
        is_valid_index = self.convert_begin_if()
4✔
1254

1255
        self.duplicate_stack_item(2)                    # slice.append(array[index])
4✔
1256
        self.duplicate_stack_item(6)
4✔
1257
        self.duplicate_stack_item(3)
4✔
1258
        self.convert_get_item()
4✔
1259
        self.convert_builtin_method_call(Builtin.SequenceAppend.build(array))
4✔
1260
        self.convert_end_if(is_valid_index)
4✔
1261

1262
        self.__insert1(OpcodeInfo.INC)              # index += 1
4✔
1263

1264
        condition_address = VMCodeMapping.instance().bytecode_size
4✔
1265
        self.duplicate_stack_top_item()         # end while index < slice_end
4✔
1266
        self.duplicate_stack_item(4)
4✔
1267
        self.convert_operation(BinaryOp.Lt)
4✔
1268
        self.convert_end_while(start_jump, condition_address)
4✔
1269

1270
        self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
4✔
1271
        self.remove_stack_top_item()        # removes from the stack the arguments and the index
4✔
1272
        self.swap_reverse_stack_items(4)    # doesn't use CLEAR opcode because this would delete
4✔
1273
        self.remove_stack_top_item()        # data from external scopes
4✔
1274
        self.remove_stack_top_item()
4✔
1275
        self.remove_stack_top_item()
4✔
1276

1277
    def convert_get_sub_sequence(self):
4✔
1278
        """
1279
        Gets a slice of an array or ByteString
1280
        """
1281
        # top: length, index, array
1282
        if len(self._stack) > 2 and isinstance(self._stack[-3], SequenceType):
4✔
1283

1284
            if self._stack[-3].stack_item in (StackItemType.ByteString,
4✔
1285
                                              StackItemType.Buffer):
1286
                self.duplicate_stack_item(2)
4✔
1287
                self.convert_operation(BinaryOp.Sub)
4✔
1288
                self.convert_get_substring()
4✔
1289
            else:
1290
                array = self._stack[-3]
4✔
1291
                self.convert_get_array_slice(array)
4✔
1292

1293
    def convert_get_sequence_beginning(self):
4✔
1294
        """
1295
        Gets the beginning slice of an array or ByteString
1296
        """
1297
        if len(self._stack) > 1 and isinstance(self._stack[-2], SequenceType):
4✔
1298
            if self._stack[-2].stack_item in (StackItemType.ByteString,
4✔
1299
                                              StackItemType.Buffer):
1300
                self.__insert1(OpcodeInfo.LEFT)
4✔
1301
                self._stack_pop()  # length
4✔
1302
                original_type = self._stack_pop()  # original array
4✔
1303
                self._stack_append(BufferType)  # left returns a buffer instead of a bytestring
4✔
1304
                self.convert_cast(original_type)
4✔
1305
            else:
1306
                array = self._stack[-2]
4✔
1307

1308
                self.convert_literal(0)
4✔
1309
                self.swap_reverse_stack_items(2)
4✔
1310
                self.convert_get_array_slice(array)
4✔
1311

1312
    def convert_get_sequence_ending(self):
4✔
1313
        """
1314
        Gets the ending slice of an array or ByteString
1315
        """
1316
        # top: array, start_slice
1317
        if len(self._stack) > 1 and isinstance(self._stack[-2], SequenceType):
4✔
1318
            if self._stack[-2].stack_item in (StackItemType.ByteString,
4✔
1319
                                              StackItemType.Buffer):
1320
                self.duplicate_stack_item(2)
4✔
1321
                self.convert_builtin_method_call(Builtin.Len)
4✔
1322
                self.swap_reverse_stack_items(2)
4✔
1323
                self.convert_operation(BinaryOp.Sub)    # gets the amount of chars that should be taken after the index
4✔
1324
                self.__insert1(OpcodeInfo.RIGHT)
4✔
1325
                self._stack_pop()  # length
4✔
1326
                original_type = self._stack_pop()  # original array
4✔
1327
                self._stack_append(BufferType)     # right returns a buffer instead of a bytestring
4✔
1328
                self.convert_cast(original_type)
4✔
1329
            else:
1330
                array = self._stack[-2]
4✔
1331

1332
                self.duplicate_stack_item(2)
4✔
1333
                self.convert_builtin_method_call(Builtin.Len)
4✔
1334

1335
                self.convert_get_array_slice(array)
4✔
1336

1337
    def convert_copy(self):
4✔
1338
        if self._stack[-1].stack_item is StackItemType.Array:
4✔
1339
            self.__insert1(OpcodeInfo.UNPACK)
4✔
1340
            self.__insert1(OpcodeInfo.PACK)    # creates a new array with the values
4✔
1341

1342
    def convert_get_stride(self):
4✔
1343
        if len(self._stack) > 1 and isinstance(self._stack[-2], SequenceType):
4✔
1344
            if self._stack[-2].stack_item in (StackItemType.ByteString, StackItemType.Buffer):
4✔
1345
                self.convert_get_substring_stride()
4✔
1346
            else:
1347
                self.convert_get_array_stride()
4✔
1348

1349
    def convert_array_negative_stride(self):
4✔
1350
        """
1351
        Converts an array to its reverse, to be able to scroll through the reversed list
1352
        """
1353
        # The logic on this function only do variable[::-z]
1354

1355
        original = self._stack[-1]
4✔
1356
        self.convert_builtin_method_call(Builtin.Reversed)
4✔
1357
        self.convert_cast(ListType())
4✔
1358
        if isinstance(original, (StrType, BytesType)):        # if self was a string/bytes, then concat the values in the array
4✔
1359
            self.duplicate_stack_top_item()
4✔
1360
            self.convert_builtin_method_call(Builtin.Len)       # index = len(array) - 1
4✔
1361
            self.convert_literal('')                            # string = ''
4✔
1362

1363
            start_jump = self.convert_begin_while()
4✔
1364
            self.duplicate_stack_item(3)
4✔
1365
            self.duplicate_stack_item(3)
4✔
1366
            str_type = self._stack[-3]
4✔
1367
            self.__insert1(OpcodeInfo.PICKITEM)
4✔
1368
            self._stack_pop()
4✔
1369
            self._stack_pop()
4✔
1370
            self._stack_append(str_type)
4✔
1371
            self.swap_reverse_stack_items(2)
4✔
1372
            self.convert_operation(BinaryOp.Concat)             # string = string + array[index]
4✔
1373

1374
            condition_address = VMCodeMapping.instance().bytecode_size
4✔
1375
            self.swap_reverse_stack_items(2)
4✔
1376
            self.__insert1(OpcodeInfo.DEC)                      # index--
4✔
1377
            self.swap_reverse_stack_items(2)
4✔
1378
            self.duplicate_stack_item(2)
4✔
1379
            self.convert_literal(0)
4✔
1380
            self.convert_operation(BinaryOp.GtE)                # if index <= 0, stop loop
4✔
1381
            self.convert_end_while(start_jump, condition_address)
4✔
1382
            self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
4✔
1383

1384
            # remove auxiliary values
1385
            self.swap_reverse_stack_items(3)
4✔
1386
            self.remove_stack_top_item()
4✔
1387
            self.remove_stack_top_item()
4✔
1388

1389
    def convert_get_substring_stride(self):
4✔
1390
        # initializing auxiliary variables
1391
        self.duplicate_stack_item(2)
4✔
1392
        self.convert_builtin_method_call(Builtin.Len)
4✔
1393
        self.convert_literal(0)                         # index = 0
4✔
1394
        self.convert_literal('')                        # substr = ''
4✔
1395

1396
        # logic verifying if substr[index] should be concatenated or not
1397
        start_jump = self.convert_begin_while()
4✔
1398
        self.duplicate_stack_item(2)
4✔
1399
        self.duplicate_stack_item(5)
4✔
1400
        self.convert_operation(BinaryOp.Mod)
4✔
1401
        self.convert_literal(0)
4✔
1402
        self.convert_operation(BinaryOp.NumEq)
4✔
1403
        is_mod_0 = self.convert_begin_if()              # if index % stride == 0, then concatenate it
4✔
1404

1405
        # concatenating substr[index] with substr
1406
        self.duplicate_stack_item(5)
4✔
1407
        self.duplicate_stack_item(3)
4✔
1408
        self.convert_literal(1)
4✔
1409
        self._stack_pop()  # length
4✔
1410
        self._stack_pop()  # start
4✔
1411
        str_type = self._stack_pop()
4✔
1412
        self.__insert1(OpcodeInfo.SUBSTR)
4✔
1413
        self._stack_append(BufferType)                  # SUBSTR returns a buffer instead of a bytestring
4✔
1414
        self.convert_cast(str_type)
4✔
1415
        self.convert_operation(BinaryOp.Concat)         # substr = substr + string[index]
4✔
1416
        self.convert_end_if(is_mod_0)
4✔
1417

1418
        # increment the index by 1
1419
        self.swap_reverse_stack_items(2)
4✔
1420
        self.__insert1(OpcodeInfo.INC)                  # index++
4✔
1421
        self.swap_reverse_stack_items(2)
4✔
1422

1423
        # verifying if it should still be in the while
1424
        condition_address = VMCodeMapping.instance().bytecode_size
4✔
1425
        self.duplicate_stack_item(2)
4✔
1426
        self.duplicate_stack_item(4)
4✔
1427
        self.convert_operation(BinaryOp.Lt)             # stop the loop when index >= len(str)
4✔
1428
        self.convert_end_while(start_jump, condition_address)
4✔
1429
        self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
4✔
1430

1431
        # removing auxiliary values
1432
        self.swap_reverse_stack_items(5)
4✔
1433
        self.remove_stack_top_item()
4✔
1434
        self.remove_stack_top_item()
4✔
1435
        self.remove_stack_top_item()
4✔
1436
        self.remove_stack_top_item()
4✔
1437

1438
    def convert_get_array_stride(self):
4✔
1439
        # initializing auxiliary variable
1440
        self.duplicate_stack_item(2)
4✔
1441
        self.convert_builtin_method_call(Builtin.Len)
4✔
1442
        self.__insert1(OpcodeInfo.DEC)                      # index = len(array) - 1
4✔
1443

1444
        # logic verifying if array[index] should be removed or not
1445
        start_jump = self.convert_begin_while()
4✔
1446
        self.duplicate_stack_item(2)
4✔
1447
        self.duplicate_stack_item(2)
4✔
1448
        self.swap_reverse_stack_items(2)
4✔
1449
        self.convert_operation(BinaryOp.Mod)
4✔
1450
        self.convert_literal(0)
4✔
1451
        self.convert_operation(BinaryOp.NumNotEq)
4✔
1452
        is_not_mod_0 = self.convert_begin_if()              # if index % stride != 0, then remove it
4✔
1453

1454
        # removing element from array
1455
        self.duplicate_stack_item(3)
4✔
1456
        self.duplicate_stack_item(2)
4✔
1457
        self.__insert1(OpcodeInfo.REMOVE)                   # array.pop(index)
4✔
1458
        self._stack_pop()
4✔
1459
        self._stack_pop()
4✔
1460
        self.convert_end_if(is_not_mod_0)
4✔
1461

1462
        # decrement 1 from index
1463
        self.__insert1(OpcodeInfo.DEC)
4✔
1464

1465
        # verifying if it should still be in the while
1466
        condition_address = VMCodeMapping.instance().bytecode_size
4✔
1467
        self.duplicate_stack_top_item()
4✔
1468
        self.__insert1(OpcodeInfo.SIGN)
4✔
1469
        self.convert_literal(-1)
4✔
1470
        self.convert_operation(BinaryOp.NumNotEq)       # stop the loop when index < 0
4✔
1471
        self.convert_end_while(start_jump, condition_address)
4✔
1472
        self.convert_end_loop_else(start_jump, self.last_code_start_address, False)
4✔
1473

1474
        # removing auxiliary values
1475
        self.remove_stack_top_item()                    # removed index from stack
4✔
1476
        self.remove_stack_top_item()                    # removed stride from stack
4✔
1477

1478
    def convert_starred_variable(self):
4✔
1479
        top_stack_item = self._stack[-1].stack_item
4✔
1480
        if top_stack_item is StackItemType.Array:
4✔
1481
            self.convert_copy()
4✔
1482
        elif top_stack_item is StackItemType.Map:
×
1483
            self.convert_builtin_method_call(Builtin.DictKeys)
×
1484
        else:
1485
            return
×
1486

1487
        self.convert_cast(Type.tuple)
4✔
1488

1489
    def convert_load_symbol(self, symbol_id: str, params_addresses: List[int] = None, is_internal: bool = False,
4✔
1490
                            class_type: Optional[UserClass] = None):
1491
        """
1492
        Converts the load of a symbol
1493

1494
        :param symbol_id: the symbol identifier
1495
        :param params_addresses: a list with each function arguments' first addresses
1496
        """
1497
        another_symbol_id, symbol = self.get_symbol(symbol_id, is_internal=is_internal)
4✔
1498

1499
        if class_type is not None and symbol_id in class_type.symbols:
4✔
1500
            symbol = class_type.symbols[symbol_id]
4✔
1501

1502
        if symbol is not Type.none:
4✔
1503
            if isinstance(symbol, Property):
4✔
1504
                symbol = symbol.getter
4✔
1505
                params_addresses = []
4✔
1506
            elif isinstance(symbol, ClassType) and params_addresses is not None:
4✔
1507
                symbol = symbol.constructor_method()
4✔
1508

1509
            if not params_addresses:
4✔
1510
                params_addresses = []
4✔
1511

1512
            if isinstance(symbol, Variable):
4✔
1513
                symbol_id = another_symbol_id
4✔
1514
                self.convert_load_variable(symbol_id, symbol, class_type)
4✔
1515
            elif isinstance(symbol, IBuiltinMethod) and symbol.body is None:
4✔
1516
                self.convert_builtin_method_call(symbol, params_addresses)
4✔
1517
            elif isinstance(symbol, Event):
4✔
1518
                self.convert_event_call(symbol)
4✔
1519
            elif isinstance(symbol, Method):
4✔
1520
                self.convert_method_call(symbol, len(params_addresses))
4✔
1521
            elif isinstance(symbol, UserClass):
4✔
1522
                self.convert_class_symbol(symbol, symbol_id)
4✔
1523

1524
    def convert_load_class_variable(self, class_type: ClassType, var_id: str, is_internal: bool = False):
4✔
1525
        variable_list = (class_type._all_variables
4✔
1526
                         if hasattr(class_type, '_all_variables') and is_internal
1527
                         else class_type.variables)
1528

1529
        if var_id in variable_list:
4✔
1530
            var = variable_list[var_id]
4✔
1531
            index = list(variable_list).index(var_id)
4✔
1532
            self.convert_literal(index)
4✔
1533
            self.convert_get_item(index_inserted_internally=True)
4✔
1534
            self._stack_pop()  # pop class type
4✔
1535
            self._stack_append(var.type)  # push variable type
4✔
1536

1537
    def convert_load_variable(self, var_id: str, var: Variable, class_type: Optional[UserClass] = None):
4✔
1538
        """
1539
        Converts the assignment of a variable
1540

1541
        :param var_id: the value to be converted
1542
        :param var: the actual variable to be loaded
1543
        """
1544
        index, local, is_arg = self._get_variable_info(var_id)
4✔
1545
        if index >= 0:
4✔
1546
            opcode = OpcodeHelper.get_load(index, local, is_arg)
4✔
1547
            op_info = OpcodeInfo.get_info(opcode)
4✔
1548

1549
            if op_info.data_len > 0:
4✔
1550
                self.__insert1(op_info, Integer(index).to_byte_array())
4✔
1551
            else:
1552
                self.__insert1(op_info)
4✔
1553
            self._stack_append(var.type)
4✔
1554

1555
        elif hasattr(var.type, 'get_value'):
4✔
1556
            # the variable is a type constant
1557
            # TODO: change this when implement class conversion
1558
            value = var.type.get_value(var_id.split(constants.ATTRIBUTE_NAME_SEPARATOR)[-1])
4✔
1559
            if value is not None:
4✔
1560
                self.convert_literal(value)
4✔
1561

1562
        elif var_id in self._globals:
4✔
1563
            another_var_id, var = self.get_symbol(var_id)
4✔
1564
            storage_key = codegenerator.get_storage_key_for_variable(var)
4✔
1565
            self._convert_builtin_storage_get_or_put(True, storage_key)
4✔
1566

1567
        elif class_type:
4✔
1568
            self.convert_load_class_variable(class_type, var_id)
4✔
1569

1570
    def convert_store_variable(self, var_id: str, value_start_address: int = None, user_class: UserClass = None):
4✔
1571
        """
1572
        Converts the assignment of a variable
1573

1574
        :param var_id: the value to be converted
1575
        """
1576
        inner_index = None
4✔
1577
        index, local, is_arg = self._get_variable_info(var_id)
4✔
1578

1579
        if user_class is None and len(self._stack) > 1 and isinstance(self._stack[-2], UserClass):
4✔
1580
            user_class = self._stack[-2]
4✔
1581

1582
        if isinstance(user_class, UserClass) and var_id in user_class.variables:
4✔
1583
            index, local, is_arg = self._get_variable_info(user_class.identifier)
4✔
1584
            inner_index = list(user_class.variables).index(var_id)
4✔
1585

1586
        if isinstance(inner_index, int):
4✔
1587
            # it's a variable from a class
1588
            self.convert_literal(inner_index)
4✔
1589
            index_address = self.bytecode_size
4✔
1590

1591
            if var_id in user_class.class_variables:
4✔
1592
                # it's a class variable
1593
                self.convert_load_variable(user_class.identifier, Variable(user_class))
4✔
1594
                no_stack_items_to_swap = 3
4✔
1595
            else:
1596
                no_stack_items_to_swap = 2
4✔
1597

1598
            self.swap_reverse_stack_items(no_stack_items_to_swap)
4✔
1599
            self.convert_set_item(index_address, index_inserted_internally=True)
4✔
1600
            return
4✔
1601

1602
        if index >= 0:
4✔
1603
            opcode = OpcodeHelper.get_store(index, local, is_arg)
4✔
1604
            if opcode is not None:
4✔
1605
                op_info = OpcodeInfo.get_info(opcode)
4✔
1606

1607
                if op_info.data_len > 0:
4✔
1608
                    self.__insert1(op_info, Integer(index).to_byte_array())
4✔
1609
                else:
1610
                    self.__insert1(op_info)
4✔
1611
                stored_type = self._stack_pop()
4✔
1612

1613
                from boa3.internal.analyser.model.optimizer import UndefinedType
4✔
1614
                if (var_id in self._current_scope.symbols or
4✔
1615
                        (var_id in self._locals and self._current_method.locals[var_id].type is UndefinedType)):
1616
                    another_symbol_id, symbol = self.get_symbol(var_id)
4✔
1617
                    if isinstance(symbol, Variable):
4✔
1618
                        var = symbol.copy()
4✔
1619
                        var.set_type(stored_type)
4✔
1620
                        self._current_scope.include_symbol(var_id, var)
4✔
1621

1622
        elif var_id in self._globals:
4✔
1623
            another_var_id, var = self.get_symbol(var_id)
4✔
1624
            storage_key = codegenerator.get_storage_key_for_variable(var)
4✔
1625
            if value_start_address is None:
4✔
1626
                value_start_address = self.bytecode_size
×
1627
            self._convert_builtin_storage_get_or_put(False, storage_key, value_start_address)
4✔
1628

1629
    def _convert_builtin_storage_get_or_put(self, is_get: bool, storage_key: bytes, arg_address: int = None):
4✔
1630
        addresses = [arg_address] if arg_address is not None else [self.bytecode_size]
4✔
1631
        if not is_get:
4✔
1632
            # must serialized before storing the value
1633
            self.convert_builtin_method_call(Interop.Serialize, addresses)
4✔
1634

1635
        self.convert_literal(storage_key)
4✔
1636
        self.convert_builtin_method_call(Interop.StorageGetContext)
4✔
1637

1638
        builtin_method = Interop.StorageGet if is_get else Interop.StoragePut
4✔
1639
        self.convert_builtin_method_call(builtin_method)
4✔
1640

1641
        if is_get:
4✔
1642
            # once the value is retrieved, it must be deserialized
1643
            self.convert_builtin_method_call(Interop.Deserialize, addresses)
4✔
1644

1645
    def _get_variable_info(self, var_id: str) -> Tuple[int, bool, bool]:
4✔
1646
        """
1647
        Gets the necessary information about the variable to get the correct opcode
1648

1649
        :param var_id: the name id of the
1650
        :return: returns the index of the variable in its scope and two boolean variables for representing the
1651
        variable scope:
1652
            `local` is True if it is a local variable and
1653
            `is_arg` is True only if the variable is a parameter of the function.
1654
        If the variable is not found, returns (-1, False, False)
1655
        """
1656
        is_arg: bool = False
4✔
1657
        local: bool = False
4✔
1658
        scope = None
4✔
1659

1660
        if var_id in self._args:
4✔
1661
            is_arg: bool = True
4✔
1662
            local: bool = True
4✔
1663
            scope = self._args
4✔
1664
        elif var_id in self._locals:
4✔
1665
            is_arg = False
4✔
1666
            local: bool = True
4✔
1667
            scope = self._locals
4✔
1668
        elif var_id in self._statics:
4✔
1669
            scope = self._statics
4✔
1670

1671
        if scope is not None:
4✔
1672
            index: int = scope.index(var_id) if var_id in scope else -1
4✔
1673
        else:
1674
            index = -1
4✔
1675

1676
        return index, local, is_arg
4✔
1677

1678
    def convert_builtin_method_call(self, function: IBuiltinMethod, args_address: List[int] = None, is_internal: bool = False):
4✔
1679
        """
1680
        Converts a builtin method function call
1681

1682
        :param function: the function to be converted
1683
        :param args_address: a list with each function arguments' first addresses
1684
        :param is_internal: whether it was called when generating other implemented symbols
1685
        """
1686
        stack_before = len(self._stack)
4✔
1687
        if args_address is None:
4✔
1688
            args_address = []
4✔
1689
        store_opcode: OpcodeInformation = None
4✔
1690
        store_data: bytes = b''
4✔
1691

1692
        if function.pack_arguments:
4✔
1693
            self.convert_new_array(len(args_address))
×
1694

1695
        if function.stores_on_slot and 0 < len(function.args) <= len(args_address):
4✔
1696
            address = args_address[-len(function.args)]
4✔
1697
            load_instr = VMCodeMapping.instance().code_map[address]
4✔
1698
            if OpcodeHelper.is_load_slot(load_instr.opcode):
4✔
1699
                store: Opcode = OpcodeHelper.get_store_from_load(load_instr.opcode)
4✔
1700
                store_opcode = OpcodeInfo.get_info(store)
4✔
1701
                store_data = load_instr.data
4✔
1702

1703
        fix_negatives = function.validate_negative_arguments()
4✔
1704
        if len(fix_negatives) > 0:
4✔
1705
            args_end_addresses = args_address[1:]
4✔
1706
            args_end_addresses.append(self.bytecode_size)
4✔
1707

1708
            if function.push_self_first():
4✔
1709
                addresses = args_end_addresses[:1] + list(reversed(args_end_addresses[1:]))
4✔
1710
            else:
1711
                addresses = list(reversed(args_end_addresses))
×
1712

1713
            for arg in sorted(fix_negatives, reverse=True):
4✔
1714
                if len(addresses) > arg:
4✔
1715
                    self.fix_negative_index(addresses[arg])
4✔
1716

1717
        self._convert_builtin_call(function, previous_stack_size=stack_before, is_internal=is_internal)
4✔
1718

1719
        if store_opcode is not None:
4✔
1720
            self._insert_jump(OpcodeInfo.JMP)
4✔
1721
            self._update_codes_without_target_to_next(self.last_code_start_address)
4✔
1722
            jump = self.last_code_start_address
4✔
1723
            self.__insert1(store_opcode, store_data)
4✔
1724
            self._update_jump(jump, VMCodeMapping.instance().bytecode_size)
4✔
1725

1726
    def _convert_builtin_call(self, builtin: IBuiltinCallable, previous_stack_size: int = None, is_internal: bool = False):
4✔
1727
        if not isinstance(previous_stack_size, int):
4✔
1728
            previous_stack_size = len(self._stack)
4✔
1729
        elif previous_stack_size < 0:
4✔
1730
            previous_stack_size = 0
×
1731

1732
        if is_internal:
4✔
1733
            builtin.generate_internal_opcodes(self)
4✔
1734
        else:
1735
            builtin.generate_opcodes(self)
4✔
1736

1737
        if isinstance(builtin, IBuiltinMethod):
4✔
1738
            if is_internal and hasattr(builtin, 'internal_call_args'):
4✔
1739
                expected_stack_after = previous_stack_size - builtin.internal_call_args
4✔
1740
            else:
1741
                expected_stack_after = previous_stack_size - builtin.args_on_stack
4✔
1742
        else:
1743
            expected_stack_after = previous_stack_size - len(builtin.args)
4✔
1744

1745
        if expected_stack_after < 0:
4✔
1746
            expected_stack_after = 0
×
1747

1748
        while expected_stack_after < len(self._stack):
4✔
1749
            self._stack_pop()
4✔
1750
        if builtin.return_type not in (None, Type.none):
4✔
1751
            self._stack_append(builtin.return_type)
4✔
1752

1753
    def convert_method_call(self, function: Method, num_args: int):
4✔
1754
        """
1755
        Converts a method function call
1756

1757
        :param function: the function to be converted
1758
        """
1759
        if function.is_init:
4✔
1760
            if num_args == len(function.args):
4✔
1761
                self.remove_stack_top_item()
×
1762
                num_args -= 1
×
1763

1764
            if num_args == len(function.args) - 1:
4✔
1765
                # if this method is a constructor and only the self argument is missing
1766
                function_result = function.type
4✔
1767
                size = len(function_result.variables) if isinstance(function_result, UserClass) else 0
4✔
1768
                if self.stack_size < 1 or not self._stack[-1].is_type_of(function_result):
4✔
1769
                    self.convert_new_empty_array(size, function_result)
×
1770

1771
        if isinstance(function.origin_class, ContractInterfaceClass):
4✔
1772
            if function.external_name is not None:
4✔
1773
                function_id = function.external_name
4✔
1774
            else:
1775
                function_id = next((symbol_id
4✔
1776
                                    for symbol_id, symbol in function.origin_class.symbols.items()
1777
                                    if symbol is function),
1778
                                   None)
1779
            self._add_to_metadata_permissions(function.origin_class, function_id)
4✔
1780

1781
            if isinstance(function_id, str):
4✔
1782
                self.convert_new_array(len(function.args))
4✔
1783
                self.convert_literal(Interop.CallFlagsType.default_value)
4✔
1784
                self.convert_literal(function_id)
4✔
1785
                self.convert_literal(function.origin_class.contract_hash.to_array())
4✔
1786
                self.convert_builtin_method_call(Interop.CallContract)
4✔
1787
                self._stack_pop()  # remove call contract 'any' result from the stack
4✔
1788
                self._stack_append(function.return_type)    # add the return type on the stack even if it is None
4✔
1789

1790
        else:
1791
            from boa3.internal.neo.vm.CallCode import CallCode
4✔
1792
            call_code = CallCode(function)
4✔
1793
            self.__insert_code(call_code)
4✔
1794
            self._update_codes_with_target(call_code)
4✔
1795

1796
            for arg in range(num_args):
4✔
1797
                self._stack_pop()
4✔
1798
            if function.is_init:
4✔
1799
                self._stack_pop()  # pop duplicated result if it's init
4✔
1800

1801
            if function.return_type is not Type.none:
4✔
1802
                self._stack_append(function.return_type)
4✔
1803

1804
    def convert_method_token_call(self, method_token_id: int):
4✔
1805
        """
1806
        Converts a method token call
1807
        """
1808
        if method_token_id >= 0:
4✔
1809
            method_token = VMCodeMapping.instance().get_method_token(method_token_id)
4✔
1810
            if method_token is not None:
4✔
1811
                opcode_info = OpcodeInfo.CALLT
4✔
1812
                self.__insert1(opcode_info, Integer(method_token_id).to_byte_array(min_length=opcode_info.data_len))
4✔
1813

1814
    def convert_event_call(self, event: Event):
4✔
1815
        """
1816
        Converts an event call
1817

1818
        :param event_id: called event identifier
1819
        :param event: called event
1820
        """
1821
        self.convert_new_array(len(event.args_to_generate), Type.list)
4✔
1822
        if event.generate_name:
4✔
1823
            self.convert_literal(event.name)
4✔
1824
        else:
1825
            self.swap_reverse_stack_items(2)
4✔
1826

1827
        from boa3.internal.model.builtin.interop.interop import Interop
4✔
1828
        self._convert_builtin_call(Interop.Notify, is_internal=True)
4✔
1829

1830
    def convert_class_symbol(self, class_type: ClassType, symbol_id: str, load: bool = True) -> Optional[int]:
4✔
1831
        """
1832
        Converts an class symbol
1833

1834
        :param class_type:
1835
        :param symbol_id:
1836
        :param load:
1837
        """
1838
        method: Method
1839
        is_safe_to_convert = False
4✔
1840

1841
        if symbol_id in class_type.variables:
4✔
1842
            return self.convert_class_variable(class_type, symbol_id, load)
4✔
1843
        elif symbol_id in class_type.properties:
4✔
1844
            symbol = class_type.properties[symbol_id]
4✔
1845
            is_safe_to_convert = True
4✔
1846
            method = symbol.getter if load else symbol.setter
4✔
1847
        elif symbol_id in class_type.instance_methods:
4✔
1848
            method = class_type.instance_methods[symbol_id]
4✔
1849
        elif symbol_id in class_type.class_methods:
4✔
1850
            method = class_type.class_methods[symbol_id]
4✔
1851
        elif isinstance(class_type, UserClass):
4✔
1852
            return self.convert_user_class(class_type, symbol_id)
4✔
1853
        else:
1854
            return
×
1855

1856
        if isinstance(method, IBuiltinMethod):
4✔
1857
            if not is_safe_to_convert:
4✔
1858
                is_safe_to_convert = len(method.args) == 0
4✔
1859

1860
            if is_safe_to_convert:
4✔
1861
                self.convert_builtin_method_call(method)
4✔
1862
        else:
1863
            self.convert_method_call(method, 0)
×
1864
        return symbol_id
4✔
1865

1866
    def convert_user_class(self, class_type: UserClass, symbol_id: str) -> Optional[int]:
4✔
1867
        """
1868
        Converts an class symbol
1869

1870
        :param class_type:
1871
        :param symbol_id:
1872
        """
1873
        start_address = self.bytecode_size
4✔
1874

1875
        if symbol_id in self._statics:
4✔
1876
            self.convert_load_variable(symbol_id, Variable(class_type))
4✔
1877
        else:
1878
            # TODO: change to create an array with the class variables' default values when they are implemented
1879
            self.convert_new_empty_array(len(class_type.class_variables), class_type)
4✔
1880

1881
        return start_address
4✔
1882

1883
    def convert_class_variable(self, class_type: ClassType, symbol_id: str, load: bool = True):
4✔
1884
        """
1885
        Converts an class variable
1886

1887
        :param class_type:
1888
        :param symbol_id:
1889
        :param load:
1890
        """
1891
        if symbol_id in class_type.variables:
4✔
1892
            index = list(class_type.variables).index(symbol_id)
4✔
1893

1894
            if load:
4✔
1895
                self.convert_literal(index)
4✔
1896
                self.convert_get_item(index_inserted_internally=True)
4✔
1897

1898
                symbol_type = class_type.variables[symbol_id].type
4✔
1899
                if self._stack[-1] != symbol_type:
4✔
1900
                    self._stack_pop()
4✔
1901
                    self._stack_append(symbol_type)
4✔
1902

1903
            return index
4✔
1904

1905
    def convert_operation(self, operation: IOperation, is_internal: bool = False):
4✔
1906
        """
1907
        Converts an operation
1908

1909
        :param operation: the operation that will be converted
1910
        :param is_internal: whether it was called when generating other implemented symbols
1911
        """
1912
        stack_before = len(self._stack)
4✔
1913
        if is_internal:
4✔
1914
            operation.generate_internal_opcodes(self)
4✔
1915
        else:
1916
            operation.generate_opcodes(self)
4✔
1917

1918
        expected_stack_after = stack_before - operation.op_on_stack
4✔
1919
        if expected_stack_after < 0:
4✔
1920
            expected_stack_after = 0
×
1921

1922
        while expected_stack_after < len(self._stack):
4✔
1923
            self._stack_pop()
4✔
1924
        self._stack_append(operation.result)
4✔
1925

1926
    def convert_assert(self, has_message: bool = False):
4✔
1927

1928
        if has_message:
4✔
1929
            asserted_type = self._stack[-2] if len(self._stack) > 1 else Type.any
4✔
1930
        else:
1931
            asserted_type = self._stack[-1] if len(self._stack) > 0 else Type.any
4✔
1932

1933
        if not isinstance(asserted_type, PrimitiveType):
4✔
1934
            if has_message:
4✔
1935
                self.swap_reverse_stack_items(2)
×
1936

1937
            len_pos = VMCodeMapping.instance().bytecode_size
4✔
1938
            # if the value is an array, a map or a struct, asserts it is not empty
1939
            self.convert_builtin_method_call(Builtin.Len)
4✔
1940
            len_code = VMCodeMapping.instance().code_map[len_pos]
4✔
1941

1942
            if asserted_type is Type.any:
4✔
1943
                # need to check in runtime
1944
                self.duplicate_stack_top_item()
4✔
1945
                self.insert_type_check(StackItemType.Array)
4✔
1946
                self._insert_jump(OpcodeInfo.JMPIF, len_code)
4✔
1947

1948
                self.duplicate_stack_top_item()
4✔
1949
                self.insert_type_check(StackItemType.Map)
4✔
1950
                self._insert_jump(OpcodeInfo.JMPIF, len_code)
4✔
1951

1952
                self.duplicate_stack_top_item()
4✔
1953
                self.insert_type_check(StackItemType.Struct)
4✔
1954
                self._insert_jump(OpcodeInfo.JMPIFNOT, 2)
4✔
1955

1956
                VMCodeMapping.instance().move_to_end(len_pos, len_pos)
4✔
1957
            if has_message:
4✔
1958
                self.swap_reverse_stack_items(2)
×
1959

1960
        self.__insert1(OpcodeInfo.ASSERT if not has_message else OpcodeInfo.ASSERTMSG)
4✔
1961
        if has_message and len(self._stack) > 1:
4✔
1962
            # pop message
1963
            self._stack_pop()
4✔
1964

1965
        if len(self._stack) > 0:
4✔
1966
            # pop assert test
1967
            self._stack_pop()
4✔
1968

1969
    def convert_abort(self, has_message: bool = False, *, is_internal: bool = False):
4✔
1970
        if not is_internal:
4✔
1971
            abort_msg_type = self._stack[-1] if len(self._stack) > 0 else Type.none
4✔
1972
            if has_message and Type.none.is_type_of(abort_msg_type):
4✔
1973
                # do not use ABORTMSG if we can detect the msg is None
1974
                has_message = not has_message
×
1975
                self._stack_pop()
×
1976

1977
        if not has_message:
4✔
1978
            self.__insert1(OpcodeInfo.ABORT)
×
1979
        else:
1980
            self.__insert1(OpcodeInfo.ABORTMSG)
4✔
1981
            self._stack_pop()
4✔
1982

1983
    def convert_new_exception(self, exception_args_len: int = 0):
4✔
1984
        if exception_args_len == 0 or len(self._stack) == 0:
4✔
1985
            self.convert_literal(Builtin.Exception.default_message)
4✔
1986

1987
        if exception_args_len > 1:
4✔
1988
            self.convert_new_array(exception_args_len)
×
1989

1990
        self._stack_pop()
4✔
1991
        self._stack_append(Type.exception)
4✔
1992

1993
    def convert_raise_exception(self, *, is_internal: bool = False):
4✔
1994
        if not is_internal:
4✔
1995
            if len(self._stack) == 0:
4✔
1996
                self.convert_literal(Builtin.Exception.default_message)
×
1997

1998
        if len(self._stack) > 0:
4✔
1999
            self._stack_pop()
4✔
2000
        self.__insert1(OpcodeInfo.THROW)
4✔
2001

2002
    def insert_opcode(self, opcode: Opcode, data: bytes = None,
4✔
2003
                      add_to_stack: List[IType] = None, pop_from_stack: bool = False):
2004
        """
2005
        Inserts one opcode into the bytecode. Used to generate built-in symbols.
2006

2007
        :param opcode: info of the opcode  that will be inserted
2008
        :param data: data of the opcode, if needed
2009
        :param add_to_stack: expected data to be included on stack, if needed
2010
        :param pop_from_stack: if needs to update stack given opcode stack items
2011
        """
2012
        op_info = OpcodeInfo.get_info(opcode)
4✔
2013
        if op_info is not None:
4✔
2014
            self.__insert1(op_info, data)
4✔
2015
            if pop_from_stack:
4✔
2016
                for _ in range(op_info.stack_items):
4✔
2017
                    self._stack_pop()
4✔
2018

2019
        if isinstance(add_to_stack, list):
4✔
2020
            for stack_item in add_to_stack:
4✔
2021
                self._stack_append(stack_item)
4✔
2022

2023
    def insert_type_check(self, type_to_check: Optional[StackItemType]):
4✔
2024
        if isinstance(type_to_check, StackItemType):
4✔
2025
            self.__insert1(OpcodeInfo.ISTYPE, type_to_check)
4✔
2026
        else:
2027
            self.__insert1(OpcodeInfo.ISNULL)
4✔
2028
        self._stack_pop()
4✔
2029
        self._stack_append(Type.bool)
4✔
2030

2031
    def __insert1(self, op_info: OpcodeInformation, data: bytes = None):
4✔
2032
        """
2033
        Inserts one opcode into the bytecode
2034

2035
        :param op_info: info of the opcode  that will be inserted
2036
        :param data: data of the opcode, if needed
2037
        """
2038
        vm_code = VMCode(op_info, data)
4✔
2039

2040
        if OpcodeHelper.has_target(op_info.opcode):
4✔
2041
            data = vm_code.raw_data
4✔
2042
            relative_address: int = Integer.from_bytes(data, signed=True)
4✔
2043
            actual_address = VMCodeMapping.instance().bytecode_size + relative_address
4✔
2044
            if (self._can_append_target
4✔
2045
                    and relative_address != 0
2046
                    and actual_address in VMCodeMapping.instance().code_map):
2047
                vm_code.set_target(VMCodeMapping.instance().code_map[actual_address])
4✔
2048
            else:
2049
                self._include_missing_target(vm_code, actual_address)
4✔
2050

2051
        self.__insert_code(vm_code)
4✔
2052
        self._update_codes_with_target(vm_code)
4✔
2053

2054
    def __insert_code(self, vm_code: VMCode):
4✔
2055
        """
2056
        Inserts one vmcode into the bytecode
2057

2058
        :param vm_code: the opcode that will be inserted
2059
        """
2060
        VMCodeMapping.instance().insert_code(vm_code)
4✔
2061

2062
    def _include_missing_target(self, vmcode: VMCode, target_address: int = 0):
4✔
2063
        """
2064
        Includes a instruction which parameter is another instruction that wasn't converted yet
2065

2066
        :param vmcode: instruction with incomplete parameter
2067
        :param target_address: target instruction expected address
2068
        :return:
2069
        """
2070
        if OpcodeHelper.has_target(vmcode.opcode):
4✔
2071
            if target_address == VMCodeMapping.instance().bytecode_size:
4✔
2072
                target_address = None
4✔
2073
            else:
2074
                self._remove_missing_target(vmcode)
4✔
2075

2076
            if target_address not in self._missing_target:
4✔
2077
                self._missing_target[target_address] = []
4✔
2078
            if vmcode not in self._missing_target[target_address]:
4✔
2079
                self._missing_target[target_address].append(vmcode)
4✔
2080

2081
    def _remove_missing_target(self, vmcode: VMCode):
4✔
2082
        """
2083
        Removes a instruction from the missing target list
2084

2085
        :param vmcode: instruction with incomplete parameter
2086
        :return:
2087
        """
2088
        if OpcodeHelper.has_target(vmcode.opcode):
4✔
2089
            for target_address, opcodes in self._missing_target.copy().items():
4✔
2090
                if vmcode in opcodes:
4✔
2091
                    opcodes.remove(vmcode)
4✔
2092
                    if len(opcodes) == 0:
4✔
2093
                        self._missing_target.pop(target_address)
4✔
2094
                    break
4✔
2095

2096
    def _check_codes_with_target(self) -> bool:
4✔
2097
        """
2098
        Verifies if there are any instructions targeting positions not included yet.
2099
        """
2100
        instance = VMCodeMapping.instance()
4✔
2101
        current_bytecode_size = instance.bytecode_size
4✔
2102
        for target_address, codes in list(self._missing_target.items()):
4✔
2103
            if target_address is not None and target_address >= current_bytecode_size:
4✔
2104
                return True
×
2105

2106
        if None in self._missing_target:
4✔
2107
            for code in self._missing_target[None]:
4✔
2108
                if OpcodeHelper.is_jump(code.info.opcode) and code.target is None:
4✔
2109
                    target = Integer.from_bytes(code.raw_data) + VMCodeMapping.instance().get_start_address(code)
4✔
2110
                    if target >= current_bytecode_size:
4✔
2111
                        return True
4✔
2112
        return False
4✔
2113

2114
    def _update_codes_with_target(self, vm_code: VMCode):
4✔
2115
        """
2116
        Verifies if there are any instructions targeting the code. If it exists, updates each instruction found
2117

2118
        :param vm_code: targeted instruction
2119
        """
2120
        instance = VMCodeMapping.instance()
4✔
2121
        vm_code_start_address = instance.get_start_address(vm_code)
4✔
2122
        for target_address, codes in list(self._missing_target.items()):
4✔
2123
            if target_address is not None and target_address <= vm_code_start_address:
4✔
2124
                for code in codes:
4✔
2125
                    code.set_target(vm_code)
4✔
2126
                self._missing_target.pop(target_address)
4✔
2127

2128
    def _update_codes_without_target_to_next(self, address: int = None):
4✔
2129
        if address is None:
4✔
2130
            address = self.bytecode_size
×
2131

2132
        instance = VMCodeMapping.instance()
4✔
2133
        vm_code = instance.get_code(address)
4✔
2134
        if vm_code is None:
4✔
2135
            return
×
2136

2137
        next_address = instance.get_end_address(vm_code)
4✔
2138
        if address in self._missing_target:
4✔
2139
            targets = self._missing_target.pop(address)
×
2140

2141
            if next_address in self._missing_target:
×
2142
                self._missing_target[next_address].extend(targets)
×
2143
            else:
2144
                self._missing_target[next_address] = targets
×
2145

2146
        if None in self._missing_target:
4✔
2147
            for code in self._missing_target[None]:
4✔
2148
                code_address = instance.get_start_address(code)
4✔
2149
                target_address = Integer.from_bytes(code.raw_data) + code_address
4✔
2150
                if target_address == address and target_address != code_address:
4✔
2151
                    data = self._get_jump_data(code.info, next_address - code_address)
4✔
2152
                    instance.update_vm_code(code, code.info, data)
4✔
2153

2154
    def set_code_targets(self):
4✔
2155
        for target, vmcodes in self._missing_target.copy().items():
4✔
2156
            if target is None:
4✔
2157
                for code in vmcodes.copy():
4✔
2158
                    relative_address: int = Integer.from_bytes(code.raw_data, signed=True)
4✔
2159
                    code_address: int = VMCodeMapping.instance().get_start_address(code)
4✔
2160
                    absolute_address = code_address + relative_address
4✔
2161
                    code.set_target(VMCodeMapping.instance().get_code(absolute_address))
4✔
2162

2163
                    vmcodes.remove(code)
4✔
2164
            else:
2165
                for code in vmcodes.copy():
×
2166
                    code.set_target(VMCodeMapping.instance().get_code(target))
×
2167
                    vmcodes.remove(code)
×
2168

2169
            if len(vmcodes) == 0:
4✔
2170
                self._missing_target.pop(target)
4✔
2171

2172
    def _insert_jump(self, op_info: OpcodeInformation, jump_to: Union[int, VMCode] = 0, insert_jump: bool = False):
4✔
2173
        """
2174
        Inserts a jump opcode into the bytecode
2175

2176
        :param op_info: info of the opcode  that will be inserted
2177
        :param jump_to: data of the opcode
2178
        :param insert_jump: whether it should be included a jump to the end before the else branch
2179
        """
2180
        if isinstance(jump_to, VMCode):
4✔
2181
            jump_to = VMCodeMapping.instance().get_start_address(jump_to) - VMCodeMapping.instance().bytecode_size
4✔
2182

2183
        if self.last_code.opcode is not Opcode.RET or insert_jump:
4✔
2184
            data: bytes = self._get_jump_data(op_info, jump_to)
4✔
2185
            self.__insert1(op_info, data)
4✔
2186
        for x in range(op_info.stack_items):
4✔
2187
            self._stack_pop()
4✔
2188

2189
    def _update_jump(self, jump_address: int, updated_jump_to: int):
4✔
2190
        """
2191
        Updates the data of a jump code in the bytecode
2192

2193
        :param jump_address: jump code start address
2194
        :param updated_jump_to: new data of the code
2195
        """
2196
        vmcode: VMCode = VMCodeMapping.instance().get_code(jump_address)
4✔
2197
        if vmcode is not None:
4✔
2198
            if updated_jump_to in VMCodeMapping.instance().code_map:
4✔
2199
                self._remove_missing_target(vmcode)
4✔
2200
                target: VMCode = VMCodeMapping.instance().get_code(updated_jump_to)
4✔
2201
                vmcode.set_target(target)
4✔
2202
            else:
2203
                data: bytes = self._get_jump_data(vmcode.info, updated_jump_to - jump_address)
4✔
2204
                VMCodeMapping.instance().update_vm_code(vmcode, vmcode.info, data)
4✔
2205
                if updated_jump_to not in VMCodeMapping.instance().code_map:
4✔
2206
                    self._include_missing_target(vmcode, updated_jump_to)
4✔
2207

2208
    def change_jump(self, jump_address: int, new_jump_opcode: Opcode):
4✔
2209
        """
2210
        Changes the type of jump code in the bytecode
2211

2212
        """
2213
        if not OpcodeHelper.is_jump(new_jump_opcode):
4✔
2214
            return
×
2215

2216
        vmcode: VMCode = VMCodeMapping.instance().get_code(jump_address)
4✔
2217
        if vmcode is not None and OpcodeHelper.is_jump(vmcode.opcode):
4✔
2218
            previous_consumed_items_from_stack = vmcode.info.stack_items
4✔
2219
            new_consumed_items_from_stack = OpcodeInfo.get_info(new_jump_opcode).stack_items
4✔
2220
            if previous_consumed_items_from_stack < new_consumed_items_from_stack:
4✔
2221
                # if previous jump doesn't have condition and the new one has
2222
                previous_stack = self._stack_states.get_state(jump_address)
4✔
2223
                items_to_consume = new_consumed_items_from_stack - previous_consumed_items_from_stack
4✔
2224

2225
                if jump_address == self.last_code_start_address:
4✔
2226
                    for _ in range(items_to_consume):
4✔
2227
                        self._stack_pop()
4✔
2228
                    target_stack_size = len(self._stack)
4✔
2229
                else:
2230
                    target_stack_size = len(previous_stack) - items_to_consume
×
2231

2232
                while len(previous_stack) > target_stack_size:
4✔
2233
                    previous_stack.pop(-1)  # consume last stack item as jump condition
4✔
2234

2235
            vmcode.set_opcode(OpcodeInfo.get_info(new_jump_opcode))
4✔
2236

2237
    def _get_jump_data(self, op_info: OpcodeInformation, jump_to: int) -> bytes:
4✔
2238
        return Integer(jump_to).to_byte_array(min_length=op_info.data_len, signed=True)
4✔
2239

2240
    def _add_to_metadata_permissions(self, contract_class: ContractInterfaceClass, method_name: str):
4✔
2241
        from boa3.internal.compiler.compiledmetadata import CompiledMetadata
4✔
2242
        CompiledMetadata.instance().add_contract_permission(contract_class.contract_hash, method_name)
4✔
2243

2244
    def duplicate_stack_top_item(self):
4✔
2245
        self.duplicate_stack_item(1)
4✔
2246

2247
    def duplicate_stack_item(self, pos: int = 0, *, expected_stack_item: IType = None):
4✔
2248
        """
2249
        Duplicates the item n back in the stack
2250

2251
        :param pos: index of the variable
2252
        """
2253
        # n = 1 -> duplicates stack top item
2254
        # n = 0 -> value varies in runtime
2255
        if pos >= 0:
4✔
2256
            opcode: Opcode = OpcodeHelper.get_dup(pos)
4✔
2257
            if opcode is Opcode.PICK:
4✔
2258
                if pos > 0:
4✔
2259
                    self.convert_literal(pos - 1)
4✔
2260
                    self._stack_pop()
4✔
2261
                elif Type.int.is_type_of(self._stack[-1]) and expected_stack_item is not None:
4✔
2262
                    self._stack_pop()
4✔
2263

2264
            op_info = OpcodeInfo.get_info(opcode)
4✔
2265
            self.__insert1(op_info)
4✔
2266
            if expected_stack_item is None:
4✔
2267
                stacked_value = self._stack[-pos]
4✔
2268
            else:
2269
                stacked_value = expected_stack_item
4✔
2270

2271
            self._stack_append(stacked_value)
4✔
2272

2273
    def move_stack_item_to_top(self, pos: int = 0):
4✔
2274
        """
2275
        Moves the item n back in the stack to the top
2276

2277
        :param pos: index of the variable
2278
        """
2279
        # n = 1 -> stack top item
2280
        # n = 0 -> value varies in runtime
2281
        # do nothing in those cases
2282
        if pos == 2:
4✔
2283
            self.swap_reverse_stack_items(2)
×
2284
        elif pos > 2:
4✔
2285
            self.convert_literal(pos - 1)
4✔
2286
            self._stack_pop()
4✔
2287
            self.__insert1(OpcodeInfo.ROLL)
4✔
2288
            stack_type = self._stack_pop(-pos)
4✔
2289
            self._stack_append(stack_type)
4✔
2290

2291
    def clear_stack(self, clear_if_in_loop: bool = False):
4✔
2292
        if not clear_if_in_loop or len(self._current_for) > 0:
4✔
2293
            for _ in range(self.stack_size):
4✔
2294
                self.__insert1(OpcodeInfo.DROP)
4✔
2295

2296
    def remove_stack_top_item(self):
4✔
2297
        self.remove_stack_item(1)
4✔
2298

2299
    def remove_stack_item(self, pos: int = 0):
4✔
2300
        """
2301
        Removes the item n from the stack
2302

2303
        :param pos: index of the variable
2304
        """
2305
        # n = 1 -> removes stack top item
2306
        if pos > 0:
4✔
2307
            opcode: Opcode = OpcodeHelper.get_drop(pos)
4✔
2308
            if opcode is Opcode.XDROP:
4✔
2309
                self.convert_literal(pos - 1)
×
2310
                self._stack_pop()
×
2311
            op_info = OpcodeInfo.get_info(opcode)
4✔
2312
            self.__insert1(op_info)
4✔
2313
            if pos > 0 and len(self._stack) > 0:
4✔
2314
                self._stack_pop(-pos)
4✔
2315

2316
    def _remove_inserted_opcodes_since(self, last_address: int, last_stack_size: Optional[int] = None):
4✔
2317
        self._stack_states.restore_state(last_address)
4✔
2318
        if VMCodeMapping.instance().bytecode_size > last_address:
4✔
2319
            # remove opcodes inserted during the evaluation of the symbol
2320
            VMCodeMapping.instance().remove_opcodes(last_address, VMCodeMapping.instance().bytecode_size)
4✔
2321

2322
        if isinstance(last_stack_size, int) and last_stack_size < self.stack_size:
4✔
2323
            # remove any additional values pushed to the stack during the evalution of the symbol
2324
            for _ in range(self.stack_size - last_stack_size):
4✔
2325
                self._stack_pop()
4✔
2326

2327
    def swap_reverse_stack_items(self, no_items: int = 0, rotate: bool = False):
4✔
2328
        # n = 0 -> value varies in runtime
2329
        if 0 <= no_items != 1:
4✔
2330
            opcode: Opcode = OpcodeHelper.get_reverse(no_items, rotate)
4✔
2331
            if opcode is Opcode.REVERSEN and no_items > 0:
4✔
2332
                self.convert_literal(no_items)
4✔
2333
            op_info = OpcodeInfo.get_info(opcode)
4✔
2334
            self.__insert1(op_info)
4✔
2335
            if opcode is Opcode.REVERSEN and no_items > 0:
4✔
2336
                self._stack_pop()
4✔
2337
            if no_items > 0:
4✔
2338
                self._stack.reverse(-no_items, rotate=rotate)
4✔
2339

2340
    def convert_init_user_class(self, class_type: ClassType):
4✔
2341
        # TODO: refactor when instance variables are implemented
2342
        if isinstance(class_type, UserClass):
4✔
2343
            # create an none-filled array with the size of the instance variables
2344
            no_instance_variables = len(class_type.instance_variables)
4✔
2345
            self.convert_new_empty_array(no_instance_variables, class_type)
4✔
2346

2347
            self.__insert1(OpcodeInfo.UNPACK)  # unpack for array concatenation
4✔
2348
            value = self._stack_pop()
4✔
2349
            self._stack_append(Type.int)
4✔
2350
            self.remove_stack_top_item()
4✔
2351

2352
            # copy the array that stores the class variables from that class
2353
            self.convert_user_class(class_type, class_type.identifier)
4✔
2354

2355
            self.__insert1(OpcodeInfo.UNPACK)  # unpack for array concatenation
4✔
2356
            self._stack_append(value)
4✔
2357
            self.convert_literal(no_instance_variables)
4✔
2358
            self.convert_operation(BinaryOp.Add)
4✔
2359

2360
            self.__insert1(OpcodeInfo.PACK)  # packs everything together
4✔
2361
            self._stack_pop()
4✔
2362

2363
    def generate_implicit_init_user_class(self, init_method: Method):
4✔
2364
        if not init_method.is_called:
4✔
2365
            return
4✔
2366

2367
        self.convert_begin_method(init_method)
4✔
2368
        class_type = init_method.return_type
4✔
2369
        for base in class_type.bases:
4✔
2370
            base_constructor = base.constructor_method()
4✔
2371
            num_args = len(base_constructor.args)
4✔
2372

2373
            for arg_id, arg_var in reversed(list(init_method.args.items())):
4✔
2374
                self.convert_load_variable(arg_id, arg_var)
4✔
2375

2376
            args_to_call = num_args - 1 if num_args > 0 else num_args
4✔
2377
            self.convert_method_call(base_constructor, args_to_call)
4✔
2378
            self.remove_stack_top_item()
4✔
2379

2380
        self.convert_end_method()
4✔
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