feat: halstead printer

pull/1878/head
devtooligan 2 years ago
parent 201b4dfcfe
commit 7eb270adef
No known key found for this signature in database
GPG Key ID: A5606B287899E7FB
  1. 103
      slither/printers/summary/halstead.py
  2. 57
      slither/utils/upgradeability.py

@ -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

@ -325,6 +325,63 @@ def encode_ir_for_compare(ir: Operation) -> str:
return ""
# pylint: disable=too-many-branches
def encode_ir_for_halstead(ir: Operation) -> str:
# operations
if isinstance(ir, Assignment):
return "assignment"
if isinstance(ir, Index):
return "index"
if isinstance(ir, Member):
return "member" # .format(ntype(ir._type))
if isinstance(ir, Length):
return "length"
if isinstance(ir, Binary):
return f"binary({str(ir.type)})"
if isinstance(ir, Unary):
return f"unary({str(ir.type)})"
if isinstance(ir, Condition):
return f"condition({encode_var_for_compare(ir.value)})"
if isinstance(ir, NewStructure):
return "new_structure"
if isinstance(ir, NewContract):
return "new_contract"
if isinstance(ir, NewArray):
return f"new_array({ntype(ir.array_type)})"
if isinstance(ir, NewElementaryType):
return f"new_elementary({ntype(ir.type)})"
if isinstance(ir, Delete):
return "delete"
if isinstance(ir, SolidityCall):
return f"solidity_call({ir.function.full_name})"
if isinstance(ir, InternalCall):
return f"internal_call({ntype(ir.type_call)})"
if isinstance(ir, EventCall): # is this useful?
return "event"
if isinstance(ir, LibraryCall):
return "library_call"
if isinstance(ir, InternalDynamicCall):
return "internal_dynamic_call"
if isinstance(ir, HighLevelCall): # TODO: improve
return "high_level_call"
if isinstance(ir, LowLevelCall): # TODO: improve
return "low_level_call"
if isinstance(ir, TypeConversion):
return f"type_conversion({ntype(ir.type)})"
if isinstance(ir, Return): # this can be improved using values
return "return" # .format(ntype(ir.type))
if isinstance(ir, Transfer):
return "transfer"
if isinstance(ir, Send):
return "send"
if isinstance(ir, Unpack): # TODO: improve
return "unpack"
if isinstance(ir, InitArray): # TODO: improve
return "init_array"
# default
raise NotImplementedError(f"encode_ir_for_halstead: {ir}")
# pylint: disable=too-many-branches
def encode_var_for_compare(var: Variable) -> str:

Loading…
Cancel
Save