From 92b6b88f39a62f9b14e83cd35953875c22077a4e Mon Sep 17 00:00:00 2001 From: devtooligan Date: Wed, 5 Jul 2023 18:00:13 -0700 Subject: [PATCH] refactor: wip --- slither/printers/summary/halstead.py | 125 ++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 4 deletions(-) diff --git a/slither/printers/summary/halstead.py b/slither/printers/summary/halstead.py index 12e422fb08..5a61c5c4c7 100644 --- a/slither/printers/summary/halstead.py +++ b/slither/printers/summary/halstead.py @@ -25,19 +25,136 @@ """ import math +from dataclasses import dataclass, field +from typing import Tuple, List, Dict from collections import OrderedDict +from slither.core.declarations import ( + Contract, + Pragma, + Import, + Function, + Modifier, +) from slither.printers.abstract_printer import AbstractPrinter from slither.slithir.variables.temporary import TemporaryVariable -from slither.utils.myprettytable import make_pretty_table -from slither.utils.upgradeability import encode_ir_for_halstead +from slither.utils.myprettytable import make_pretty_table, MyPrettyTable +from slither.utils.upgradeability import encode_ir_for_halstead # TODO: Add to slither/utils/halstead +class HalsteadContractMetrics: + """Class to hold the Halstead metrics for a single contract.""" + # TODO: Add to slither/utils/halstead + contract: Contract + all_operators: List[str] = [] + all_operands: List[str] = [] + n1: int + n2: int + N1: int + N2: int + n: int + N: int + S: float + V: float + D: float + E: float + T: float + B: float -def compute_halstead(contracts: list) -> tuple: + def __post_init__(self): + if (len(self.all_operators == 0)): + self.populate_operators_and_operands() + self.compute_metrics() + + def populate_operators_and_operands(self): + """Populate the operators and operands lists.""" + operators = [] + operands = [] + for func in self.contract.functions: + for node in func.nodes: + for operation in node.irs: + # use operation.expression.type to get the unique operator type + encoded_operator = encode_ir_for_halstead(operation) + operators.append(encoded_operator) + + # use operation.used to get the operands of the operation ignoring the temporary variables + operands.extend([ + op for op in operation.used if not isinstance(op, TemporaryVariable) + ]) + self.all_operators.extend(operators) + self.all_operands.extend(operands) + + def compute_metrics(self, all_operators): + """Compute the Halstead metrics.""" + self.n1 = len(set(self.all_operators)) + self.n2 = len(set(self.all_operands)) + self.N1 = len(self.all_operators) + self.N2 = len(self.all_operands) + self.n = self.n1 + self.n2 + self.N = self.N1 + self.N2 + self.S = self.n1 * math.log2(self.n1) + self.n2 * math.log2(self.n2) + self.V = self.N * math.log2(self.n) + self.D = (self.n1 / 2) * (self.N2 / self.n2) + self.E = self.D * self.V + self.T = self.E / 18 + self.B = (self.E ** (2 / 3)) / 3000 + +@dataclass +class MetricsInfo: + title: str + keys: List[str] + pretty_table: MyPrettyTable + txt: str + + +class HalsteadMetrics: + """Class to hold the Halstead metrics for all contracts and methods for reporting.""" + contracts: List[Contract] + contract_metrics: Dict[Contract, HalsteadContractMetrics] + title: str = "Halstead complexity metrics" + core: MetricsInfo + extended1: MetricsInfo + extended2: MetricsInfo + + def __post_init__(self): + for contract in self.contracts: + self.contract_metrics[contract.name] = HalsteadContractMetrics(contract) + + if len(self.contracts) > 1: + self.contract_metrics["ALL CONTRACTS"] = self.get_combined_metrics() + + + self.update_core_metrics(contract) + extended1 = self.update_extended_metrics1(contract) + extended2 = self.update_extended_metrics2(contract) + self.update_extended_metrics1(contract) + self.update_extended_metrics2(contract) + + pretty_table = make_pretty_table( + ["Contract", *keys], + data=all_data, + + ) + + txt = "f" + + def update_core_metrics(self): + """Return the core metrics section.""" + title = "Core metrics" + keys = [ + "Total Operators", + "Unique Operators", + "Total Operands", + "Unique Operands", + ] + + return MetricsInfo(title=title, keys=keys, pretty_table=pretty_table, txt=txt) + + +def compute_halstead(contracts: list) -> Tuple[Dict, Dict, Dict]: """Used to compute the Halstead complexity metrics for a list of contracts. Args: contracts: list of contracts. Returns: - Halstead metrics as a tuple of two OrderedDicts (core_metrics, extended_metrics) + Halstead metrics as a tuple of three OrderedDicts (core_metrics, extended_metrics1, extended_metrics2) which each contain one key per contract. The value of each key is a dict of metrics. In addition to one key per contract, there is a key for "ALL CONTRACTS" that contains