from utils import Colors, init_interpret from opcodes import WASM_OP_Code from section_structs import ( Func_Body, WASM_Ins, Resizable_Limits, Memory_Section, ) from execute import * import os import sys import signal # McCabe cyclomatic complexity metric class Metric: def __init__(self, code_section): self.code_section = code_section self.metric = [] self.soc = [] def mccabe(self): soc = 0 Edges = 1 Nodes = 1 for funcs in self.code_section.func_bodies: for ins in funcs.code: soc += 1 # print(repr(ins.opcodeint)) if ( ins.opcodeint == 4 or ins.opcodeint == 5 or ins.opcodeint == 12 or ins.opcodeint == 13 or ins.opcodeint == 14 ): Nodes += 2 Edges += 4 elif ins.opcode == 3: Nodes += 2 Edges += 3 else: pass self.metric.append(Edges - Nodes + 1) self.soc.append(soc) soc = 0 Edges = 1 Nodes = 1 def getMcCabe(self): return self.metric def getSOC(self): return self.soc # handles the debug option --memdump. dumps the contents of linear memories. def DumpLinearMems(linear_memories, threshold): count = int() strrep = [] linmem_cnt = int() for lin_mem in linear_memories: print("-----------------------------------------") print( Colors.blue + Colors.BOLD + "Linear Memory " + repr(linmem_cnt) + " :" + Colors.ENDC ) for byte in lin_mem: if count >= threshold: break if count % 16 == 0: for ch in strrep: # @DEVI-line feed messes the pretty format up if ord(ch) != 10: print(Colors.green + " " + ch + Colors.ENDC, end="") else: pass print() strrep = [] print(Colors.cyan + hex(count), ":\t" + Colors.ENDC, end="") strrep.append(str(chr(byte))) print( Colors.blue + format(byte, "02x") + " " + Colors.ENDC, end="", ) else: strrep += str(chr(byte)) print( Colors.blue + format(byte, "02x") + " " + Colors.ENDC, end="", ) count += 1 count = 0 print() # handles the debug options --idxspc. dumps the index spaces. def DumpIndexSpaces(machinestate): print("-----------------------------------------") print(Colors.green + "Function Index Space: " + Colors.ENDC) for iter in machinestate.Index_Space_Function: print(Colors.blue + repr(iter) + Colors.ENDC) print("-----------------------------------------") print(Colors.green + "Globa Index Space: " + Colors.ENDC) for iter in machinestate.Index_Space_Global: print(Colors.blue + repr(iter) + Colors.ENDC) print("-----------------------------------------") print(Colors.green + "Linear Memory Index Space: " + Colors.ENDC) for iter in machinestate.Index_Space_Linear: print(Colors.blue + repr(iter) + Colors.ENDC) print("-----------------------------------------") print(Colors.green + "Table Index Space: " + Colors.ENDC) for iter in machinestate.Index_Space_Table: print(Colors.blue + repr(iter) + Colors.ENDC) print("-----------------------------------------") # WIP-the Truebit Machine class class TBMachine: def __init__(self): # bytearray of size PAGE_SIZE self.Linear_Memory = [] self.Stack_Label = list() self.Stack_Label_Height = int() self.Stack_Control_Flow = list() self.Stack_Call = list() self.Stack_Value = list() self.Stack_Omni = list() self.Vector_Globals = list() self.Index_Space_Function = list() self.Index_Space_Global = list() self.Index_Space_Linear = list() self.Index_Space_Table = list() self.Index_Space_Locals = list() self.Index_Space_Label = list() # handles the initialization of the WASM machine class TBInit: def __init__(self, module, machinestate): self.module = module self.machinestate = machinestate # a convenience function that runs the methods of the class. all methods # can be called separately manually as well. def run(self): self.InitFuncIndexSpace() self.InitGlobalIndexSpace() self.InitLinearMemoryIndexSpace() self.InitTableIndexSpace() self.InitializeLinearMemory() def InitFuncIndexSpace(self): if self.module.import_section is not None: for iter in self.module.import_section.import_entry: if iter.kind == 0: name = str() for i in iter.field_str: name += str(chr(i)) self.machinestate.Index_Space_Function.append(name) if self.module.function_section is not None: for iter in self.module.function_section.type_section_index: self.machinestate.Index_Space_Function.append(iter) def InitGlobalIndexSpace(self): if self.module.import_section is not None: for iter in self.module.import_section.import_entry: if iter.kind == 3: name = str() for i in iter.field_str: name += str(chr(i)) self.machinestate.Index_Space_Global.append(name) if self.module.global_section is not None: for iter in self.module.global_section.global_variables: self.machinestate.Index_Space_Global.append(iter.init_expr) def InitLinearMemoryIndexSpace(self): if self.module.import_section is not None: for iter in self.module.import_section.import_entry: if iter.kind == 2: name = str() for i in iter.field_str: name += str(chr(i)) self.machinestate.Index_Space_Linear.append(name) if self.module.memory_section is not None: for iter in self.module.memory_section.memory_types: self.machinestate.Index_Space_Linear.append(iter.initial) def InitTableIndexSpace(self): if self.module.import_section is not None: for iter in self.module.import_section.import_entry: if iter.kind == 1: name = str() for i in iter.field_str: name += str(chr(i)) self.machinestate.Index_Space_Table.append(name) if self.module.table_section is not None: for iter in self.module.table_section.table_types: self.machinestate.Index_Space_Table.append(iter.element_type) def InitializeLinearMemory(self): # @DEVI-we could try to pack the data in the linear memory ourselve to # decrease the machinestate size if self.module.memory_section is None: rsz_limits = Resizable_Limits() self.module.memory_section = Memory_Section() self.module.memory_section.memory_types = [rsz_limits] self.module.memory_section.count = 1 for _ in self.module.memory_section.memory_types: self.machinestate.Linear_Memory.append( bytearray(WASM_OP_Code.PAGE_SIZE) ) if self.module.data_section is not None: for iter in self.module.data_section.data_segments: count = int() for byte in iter.data: self.machinestate.Linear_Memory[iter.index][ init_interpret(iter.offset) + count ] = byte count += 1 # returns the machinestate def getInits(self): return self.machinestate # WIP-holds the run-rime data structures for a wasm machine class RTE: def __init__(self): # Stack_Control_Flow = list() # Stack_Value = list() # Vector_Locals = list() # Current_Position = int() # Local_Stacks = list() pass def genFuncLocalStack(self, func_body): pass # palceholder for the class that holds the validation functions class ModuleValidation: def __init__(self, module): self.module = module def TypeSection(self): pass def ImportSection(self): pass def FunctionSection(self): pass def TableSection(self): pass def MemorySection(self): pass def GlobalSection(self): pass def ExportSection(self): pass def StartSection(self): pass def ElementSection(self): pass def CodeSection(self): pass def DataSection(self): pass def TBCustom(self): pass def ValidateAll(self): self.TypeSection() self.ImportSection() self.FunctionSection() self.TableSection() self.MemorySection() self.GlobalSection() self.ExportSection() self.StartSection() self.ElementSection() self.CodeSection() self.DataSection() self.TBCustom() return True # a convinience class that handles the initialization of the wasm machine and # interpretation of the code. class VM: def __init__(self, modules): self.modules = modules self.machinestate = TBMachine() # @DEVI-FIXME- the first implementation is single-module only self.init = TBInit(self.modules[0], self.machinestate) self.init.run() self.machinestate = self.init.getInits() self.start_function = Func_Body() self.ins_cache = WASM_Ins() self.executewasm = Execute(self.machinestate) self.totGas = int() self.metric = Metric(modules[0].code_section) self.parseflags = None def setFlags(self, parseflags): self.parseflags = parseflags def getState(self): return self.machinestate def initLocalIndexSpace(self, local_count): for i in range(0, local_count): self.machinestate.Index_Space_Locals.append(0) def getStartFunctionIndex(self): if self.modules[0].start_section is None: if self.parseflags.entry is None: raise Exception( Colors.red + "module does not have a start section. no function index was provided with the --entry option.quitting..." + Colors.ENDC ) else: start_index = int(self.parseflags.entry) else: print(Colors.green + "found start section: " + Colors.ENDC, end="") start_index = self.modules[0].start_section.function_section_index print( Colors.blue + Colors.BOLD + "running function at index " + repr(start_index) + Colors.ENDC ) if start_index > len(self.modules[0].code_section.func_bodies) - 1: raise Exception( Colors.red + "invalid function index: the function index does not exist." + Colors.ENDC ) return start_index def getStartFunctionBody(self): start_index = self.getStartFunctionIndex() if isinstance(start_index, int): self.start_function = self.modules[0].code_section.func_bodies[ start_index ] elif isinstance(start_index, str): # we have to import the function from another module/library. we # assume sys calls are not present.:w pass else: raise Exception( Colors.red + "invalid entry for start function index" + Colors.ENDC ) def execute(self): print( Colors.blue + Colors.BOLD + "running module with code: " + Colors.ENDC ) for ins in self.start_function.code: print( Colors.purple + repr(ins.opcode) + " " + repr(ins.operands) + Colors.ENDC ) for ins in self.start_function.code: self.executewasm.getInstruction(ins.opcodeint, ins.operands) self.executewasm.callExecuteMethod() self.getState() # pre-execution hook def startHook(self): if self.parseflags.metric: for mem in self.modules[0].memory_section.memory_types: self.executewasm.chargeGasMem(mem.initial) self.metric.mccabe() print( Colors.red + "mccabe: " + repr(self.metric.getMcCabe()) + Colors.ENDC ) print( Colors.red + "soc: " + repr(self.metric.getSOC()) + Colors.ENDC ) # post-execution hook def endHook(self): if self.parseflags.gas: self.totGas = self.executewasm.getOPGas() print( Colors.red + "total gas cost: " + repr(self.totGas) + Colors.ENDC ) if self.machinestate.Stack_Omni: print( Colors.green + "stack top: " + repr(self.machinestate.Stack_Omni.pop()) + Colors.ENDC ) # a convinience method def run(self): self.startHook() self.getStartFunctionBody() self.initLocalIndexSpace(self.start_function.local_count) self.execute() self.endHook() # a wrapper class for VM. it timeouts instructions that take too long to # execute. class Judicator: def __init__(self, op_time_table, module): self.op_time_table = op_time_table self.vm = VM(modules) self.vm.getStartFunctionBody() def overseer(self): # @DEVI- forking introduces a new source of non-determinism pid = os.fork() # child process if pid == 0: sys.stdout = open("./jstdout", "w") sys.stderr = open("./jstderr", "w") self.vm.execute() sys.exit() # parent process if pid > 0: cpid, status = os.waitpid(pid, 0) if status == 0: print("overseer child exited successfully.") else: print("overseer child exited with non-zero.") # pid < 0 else: raise Exception( Colors.red + "could not fork judicator overseer." + Colors.ENDC ) def setup(self): signal.signal(signal.SIGALRM, self.to_sighandler) def set_alarm(t): signal.alarm(t) def to_sighandler(signum, frame): print(Colors.red + "execution time out..." + Colors.ENDC) raise Exception(Colors.red + "execution time out" + Colors.ENDC) def run(self): self.setup() self.set_alaram(10) self.overseer()