from copy import copy
import fdg.global_config
from fdg.output_data import print_coverage
from mythril.laser.plugin.plugins.coverage import InstructionCoveragePlugin
import numpy as np
[docs]
class FunctionCoverage():
    def __init__(self,coveragePlugin:InstructionCoveragePlugin):
        self.coverage_plugin=coveragePlugin # the plugin that record the instructions visited.
        self.function_instruction_indices= {} # key: function pure name(only pure name is available from SolidityContract), value: the indices of instructions belonging to the function
        self.contract_bytecode='' # the bytecode of the contract that is used to retrieve the instruction coverage status from the coverage plugin
        self.function_coverage={}
        self.coverage=0
        self.deep_functions=[]
        self.deep_functions_1st_time = []  # record how many deep functions are there
[docs]
    def feed_function_intruction_indices(self, function_instruction_indices:dict):
        self.function_instruction_indices=function_instruction_indices
        # initialize coverage for each function except constructor
        for ftn, ftn_instr_list in self.function_instruction_indices.items():
            # if ftn=='constructor' or ftn=='fallback':continue
            if ftn == 'constructor': continue
            if len(ftn_instr_list)==0:continue
            self.function_coverage[ftn] = 0 / len(ftn_instr_list) 
[docs]
    def set_runtime_bytecode(self,runtime_bytecode:str):
        self.contract_bytecode=runtime_bytecode 
[docs]
    def print_coverage(self):
        print_coverage(self.coverage,self.function_coverage,'coverage') 
[docs]
    def get_deep_functions_1st_time(self):
        return [ftn for ftn,_ in self.deep_functions_1st_time] 
[docs]
    def compute_contract_coverage(self,runtime_bytecode:str):
        if runtime_bytecode in self.coverage_plugin.coverage.keys():
            # get the instruction list belonging to the contract
            code_cov = self.coverage_plugin.coverage[runtime_bytecode ]
            self.coverage = sum(code_cov[1]) / float(code_cov[0]) * 100 
[docs]
    def compute_coverage(self):
        if self.contract_bytecode in self.coverage_plugin.coverage.keys():
            code_cov = self.coverage_plugin.coverage[self.contract_bytecode]
            self.coverage = sum(code_cov[1]) / float(code_cov[0]) * 100 # get contract coverage
            # compute coverage for each function
            instructions_cover_record = code_cov[1]
            if len(instructions_cover_record) > 0:
                instr_array = np.array(instructions_cover_record)
                for ftn,cov in self.function_coverage.items():
                    if cov==100:continue
                    ftn_instruction_indices=self.function_instruction_indices[ftn]
                    try:
                        status = instr_array[ftn_instruction_indices]
                        cov_instr = sum(status)
                        cov = cov_instr / float(len(status)) * 100
                        self.function_coverage[ftn]=cov
                    except IndexError:
                        print(f'The instructions of {ftn} are not all in the target contract.')
                        continue 
[docs]
    def get_contract_coverage(self):
        return self.coverage 
[docs]
    def get_coverage_for_a_function(self,ftn_name:str):
        if ftn_name in self.function_coverage.keys():
            return self.function_coverage[ftn_name]
        else: return -1 
[docs]
    def compute_deep_functions(self):
        """
        get a deep functions based the code coverage of each functions
        :return:
        """
        deep_ftn_coverage=[]
        for ftn_name, coverage in self.function_coverage.items():
            if coverage==0:
                print(f'{ftn_name}:{coverage} is not considered')
                continue
            if coverage<fdg.global_config.function_coverage_threshold:
                deep_ftn_coverage.append((ftn_name,coverage))
        self.deep_functions=deep_ftn_coverage
        if len(self.deep_functions_1st_time)==0:
            self.deep_functions_1st_time=self.deep_functions
        # order functions based on coverage
        self.deep_functions.sort(key=lambda x:x[1])
        return self.deep_functions 
[docs]
    def get_deep_functions(self)->list:
        """
        :return: a list of numbers indicating deep functions
        """
        return self.deep_functions 
[docs]
    def print_not_covered_instructions(self,ftn_name:str,instruction_list:list):
        if self.contract_bytecode in self.coverage_plugin.coverage.keys():
            code_cov = self.coverage_plugin.coverage[self.contract_bytecode]
             # compute coverage for each function
            instructions_cover_record = code_cov[1]
            if len(instructions_cover_record) > 0:
                instr_array = np.array(instructions_cover_record)
                if ftn_name in self.function_instruction_indices.keys():
                    ftn_instr_indices=self.function_instruction_indices[ftn_name]
                    print(f'== all instructions for {ftn_name}')
                    for instr_idx in ftn_instr_indices:
                        print(f'  {instruction_list[instr_idx]}')
                    print(f'== uncoverd instructions for {ftn_name}')
                    for instr_idx in ftn_instr_indices:
                        if not instr_array[instr_idx]:
                            print(f'  {instruction_list[instr_idx]}') 
 
if __name__=='__main__':
    a=np.array(list(range(10)))
    print(a)
    print(a[[2,3,4]])