|
|
|
@ -10,11 +10,13 @@ |
|
|
|
|
N1 = the total number of operators |
|
|
|
|
N2 = the total number of operands |
|
|
|
|
|
|
|
|
|
Extended metrics: |
|
|
|
|
Extended metrics1: |
|
|
|
|
n = n1 + n2 # Program vocabulary |
|
|
|
|
N = N1 + N2 # Program length |
|
|
|
|
S = n1 * log2(n1) + n2 * log2(n2) # Estimated program length |
|
|
|
|
V = N * log2(n) # Volume |
|
|
|
|
|
|
|
|
|
Extended metrics2: |
|
|
|
|
D = (n1 / 2) * (N2 / n2) # Difficulty |
|
|
|
|
E = D * V # Effort |
|
|
|
|
T = E / 18 seconds # Time required to program |
|
|
|
@ -27,6 +29,7 @@ from collections import OrderedDict |
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def compute_halstead(contracts: list) -> tuple: |
|
|
|
@ -42,18 +45,21 @@ def compute_halstead(contracts: list) -> tuple: |
|
|
|
|
|
|
|
|
|
core_metrics: |
|
|
|
|
{"contract1 name": { |
|
|
|
|
"n1_unique_operators": n1, |
|
|
|
|
"n2_unique_operands": n1, |
|
|
|
|
"N1_total_operators": N1, |
|
|
|
|
"n1_unique_operators": n1, |
|
|
|
|
"N2_total_operands": N2, |
|
|
|
|
"n2_unique_operands": n1, |
|
|
|
|
}} |
|
|
|
|
|
|
|
|
|
extended_metrics: |
|
|
|
|
extended_metrics1: |
|
|
|
|
{"contract1 name": { |
|
|
|
|
"n_vocabulary": n1 + n2, |
|
|
|
|
"N_prog_length": N1 + N2, |
|
|
|
|
"S_est_length": S, |
|
|
|
|
"V_volume": V, |
|
|
|
|
}} |
|
|
|
|
extended_metrics2: |
|
|
|
|
{"contract1 name": { |
|
|
|
|
"D_difficulty": D, |
|
|
|
|
"E_effort": E, |
|
|
|
|
"T_time": T, |
|
|
|
@ -62,7 +68,8 @@ def compute_halstead(contracts: list) -> tuple: |
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
core = OrderedDict() |
|
|
|
|
extended = OrderedDict() |
|
|
|
|
extended1 = OrderedDict() |
|
|
|
|
extended2 = OrderedDict() |
|
|
|
|
all_operators = [] |
|
|
|
|
all_operands = [] |
|
|
|
|
for contract in contracts: |
|
|
|
@ -72,9 +79,9 @@ def compute_halstead(contracts: list) -> tuple: |
|
|
|
|
for node in func.nodes: |
|
|
|
|
for operation in node.irs: |
|
|
|
|
# use operation.expression.type to get the unique operator type |
|
|
|
|
operator_type = operation.expression.type |
|
|
|
|
operators.append(operator_type) |
|
|
|
|
all_operators.append(operator_type) |
|
|
|
|
encoded_operator = encode_ir_for_halstead(operation) |
|
|
|
|
operators.append(encoded_operator) |
|
|
|
|
all_operators.append(encoded_operator) |
|
|
|
|
|
|
|
|
|
# use operation.used to get the operands of the operation ignoring the temporary variables |
|
|
|
|
new_operands = [ |
|
|
|
@ -82,13 +89,21 @@ def compute_halstead(contracts: list) -> tuple: |
|
|
|
|
] |
|
|
|
|
operands.extend(new_operands) |
|
|
|
|
all_operands.extend(new_operands) |
|
|
|
|
(core[contract.name], extended[contract.name]) = _calculate_metrics(operators, operands) |
|
|
|
|
core["ALL CONTRACTS"] = OrderedDict() |
|
|
|
|
extended["ALL CONTRACTS"] = OrderedDict() |
|
|
|
|
(core["ALL CONTRACTS"], extended["ALL CONTRACTS"]) = _calculate_metrics( |
|
|
|
|
all_operators, all_operands |
|
|
|
|
) |
|
|
|
|
return (core, extended) |
|
|
|
|
( |
|
|
|
|
core[contract.name], |
|
|
|
|
extended1[contract.name], |
|
|
|
|
extended2[contract.name], |
|
|
|
|
) = _calculate_metrics(operators, operands) |
|
|
|
|
if len(contracts) > 1: |
|
|
|
|
core["ALL CONTRACTS"] = OrderedDict() |
|
|
|
|
extended1["ALL CONTRACTS"] = OrderedDict() |
|
|
|
|
extended2["ALL CONTRACTS"] = OrderedDict() |
|
|
|
|
( |
|
|
|
|
core["ALL CONTRACTS"], |
|
|
|
|
extended1["ALL CONTRACTS"], |
|
|
|
|
extended2["ALL CONTRACTS"], |
|
|
|
|
) = _calculate_metrics(all_operators, all_operands) |
|
|
|
|
return (core, extended1, extended2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# pylint: disable=too-many-locals |
|
|
|
@ -115,22 +130,24 @@ def _calculate_metrics(operators, operands): |
|
|
|
|
T = E / 18 |
|
|
|
|
B = (E ** (2 / 3)) / 3000 |
|
|
|
|
core_metrics = { |
|
|
|
|
"n1_unique_operators": n1, |
|
|
|
|
"n2_unique_operands": n2, |
|
|
|
|
"N1_total_operators": N1, |
|
|
|
|
"N2_total_operands": N2, |
|
|
|
|
"Total Operators": N1, |
|
|
|
|
"Unique Operators": n1, |
|
|
|
|
"Total Operands": N2, |
|
|
|
|
"Unique Operands": n2, |
|
|
|
|
} |
|
|
|
|
extended_metrics = { |
|
|
|
|
"n_vocabulary": str(n1 + n2), |
|
|
|
|
"N_prog_length": str(N1 + N2), |
|
|
|
|
"S_est_length": f"{S:.0f}", |
|
|
|
|
"V_volume": f"{V:.0f}", |
|
|
|
|
"D_difficulty": f"{D:.0f}", |
|
|
|
|
"E_effort": f"{E:.0f}", |
|
|
|
|
"T_time": f"{T:.0f}", |
|
|
|
|
"B_bugs": f"{B:.3f}", |
|
|
|
|
extended_metrics1 = { |
|
|
|
|
"Vocabulary": str(n1 + n2), |
|
|
|
|
"Program Length": str(N1 + N2), |
|
|
|
|
"Estimated Length": f"{S:.0f}", |
|
|
|
|
"Volume": f"{V:.0f}", |
|
|
|
|
} |
|
|
|
|
return (core_metrics, extended_metrics) |
|
|
|
|
extended_metrics2 = { |
|
|
|
|
"Difficulty": f"{D:.0f}", |
|
|
|
|
"Effort": f"{E:.0f}", |
|
|
|
|
"Time": f"{T:.0f}", |
|
|
|
|
"Estimated Bugs": f"{B:.3f}", |
|
|
|
|
} |
|
|
|
|
return (core_metrics, extended_metrics1, extended_metrics2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Halstead(AbstractPrinter): |
|
|
|
@ -143,24 +160,30 @@ class Halstead(AbstractPrinter): |
|
|
|
|
if len(self.contracts) == 0: |
|
|
|
|
return self.generate_output("No contract found") |
|
|
|
|
|
|
|
|
|
core, extended = compute_halstead(self.contracts) |
|
|
|
|
core, extended1, extended2 = compute_halstead(self.contracts) |
|
|
|
|
|
|
|
|
|
# Core metrics: operations and operands |
|
|
|
|
txt = "\n\nHalstead complexity core metrics:\n" |
|
|
|
|
keys = list(core[self.contracts[0].name].keys()) |
|
|
|
|
table1 = make_pretty_table(["Contract", *keys], core, False) |
|
|
|
|
txt += str(table1) + "\n" |
|
|
|
|
table_core = make_pretty_table(["Contract", *keys], core, False) |
|
|
|
|
txt += str(table_core) + "\n" |
|
|
|
|
|
|
|
|
|
# Extended metrics1: vocabulary, program length, estimated length, volume |
|
|
|
|
txt += "\nHalstead complexity extended metrics1:\n" |
|
|
|
|
keys = list(extended1[self.contracts[0].name].keys()) |
|
|
|
|
table_extended1 = make_pretty_table(["Contract", *keys], extended1, False) |
|
|
|
|
txt += str(table_extended1) + "\n" |
|
|
|
|
|
|
|
|
|
# Extended metrics: volume, difficulty, effort, time, bugs |
|
|
|
|
# TODO: should we break this into 2 tables? currently 119 chars wide |
|
|
|
|
txt += "\nHalstead complexity extended metrics:\n" |
|
|
|
|
keys = list(extended[self.contracts[0].name].keys()) |
|
|
|
|
table2 = make_pretty_table(["Contract", *keys], extended, False) |
|
|
|
|
txt += str(table2) + "\n" |
|
|
|
|
# Extended metrics2: difficulty, effort, time, bugs |
|
|
|
|
txt += "\nHalstead complexity extended metrics2:\n" |
|
|
|
|
keys = list(extended2[self.contracts[0].name].keys()) |
|
|
|
|
table_extended2 = make_pretty_table(["Contract", *keys], extended2, False) |
|
|
|
|
txt += str(table_extended2) + "\n" |
|
|
|
|
|
|
|
|
|
res = self.generate_output(txt) |
|
|
|
|
res.add_pretty_table(table1, "Halstead core metrics") |
|
|
|
|
res.add_pretty_table(table2, "Halstead extended metrics") |
|
|
|
|
res.add_pretty_table(table_core, "Halstead core metrics") |
|
|
|
|
res.add_pretty_table(table_extended1, "Halstead extended metrics1") |
|
|
|
|
res.add_pretty_table(table_extended2, "Halstead extended metrics2") |
|
|
|
|
self.info(txt) |
|
|
|
|
|
|
|
|
|
return res |
|
|
|
|