feat: add CBO

pull/1895/head
devtooligan 1 year ago
parent 314364eeb2
commit 14c9761da2
No known key found for this signature in database
GPG Key ID: A5606B287899E7FB
  1. 1
      slither/printers/all_printers.py
  2. 77
      slither/printers/summary/ck.py
  3. 22
      slither/utils/myprettytable.py

@ -21,3 +21,4 @@ from .summary.evm import PrinterEVM
from .summary.when_not_paused import PrinterWhenNotPaused from .summary.when_not_paused import PrinterWhenNotPaused
from .summary.declaration import Declaration from .summary.declaration import Declaration
from .functions.dominator import Dominator from .functions.dominator import Dominator
from .summary.martin import Martin

@ -6,18 +6,19 @@
- Response For a Class (RFC) is a metric that measures the number of unique method calls within a class. - Response For a Class (RFC) is a metric that measures the number of unique method calls within a class.
- Number of Children (NOC) is a metric that measures the number of children a class has. - Number of Children (NOC) is a metric that measures the number of children a class has.
- Depth of Inheritance Tree (DIT) is a metric that measures the number of parent classes a class has. - Depth of Inheritance Tree (DIT) is a metric that measures the number of parent classes a class has.
- Coupling Between Object Classes (CBO) is a metric that measures the number of classes a class is coupled to.
Not implemented: Not implemented:
- Lack of Cohesion of Methods (LCOM) is a metric that measures the lack of cohesion in methods. - Lack of Cohesion of Methods (LCOM) is a metric that measures the lack of cohesion in methods.
- Weighted Methods per Class (WMC) is a metric that measures the complexity of a class. - Weighted Methods per Class (WMC) is a metric that measures the complexity of a class.
- Coupling Between Object Classes (CBO) is a metric that measures the number of classes a class is coupled to.
""" """
from typing import Tuple from typing import Tuple
from slither.printers.abstract_printer import AbstractPrinter from slither.utils.colors import bold
from slither.utils.myprettytable import make_pretty_table from slither.utils.myprettytable import make_pretty_table
from slither.slithir.operations.high_level_call import HighLevelCall from slither.slithir.operations.high_level_call import HighLevelCall
from slither.utils.colors import bold from slither.printers.abstract_printer import AbstractPrinter
from slither.printers.summary.martin import compute_coupling
def compute_dit(contract, depth=0): def compute_dit(contract, depth=0):
@ -95,37 +96,41 @@ def compute_metrics(contracts):
for inherited in contracts for inherited in contracts
} }
for c in contracts: # We pass 0 for the 2nd arg (abstractness) because we only care about the coupling metrics (Ca and Ce)
(state_variables, constants, immutables, public_getters) = count_variables(c) coupling = compute_coupling(contracts, 0)
for contract in contracts:
(state_variables, constants, immutables, public_getters) = count_variables(contract)
rfc = public_getters # add 1 for each public getter rfc = public_getters # add 1 for each public getter
metrics1[c.name] = { metrics1[contract.name] = {
"State variables": state_variables, "State variables": state_variables,
"Constants": constants, "Constants": constants,
"Immutables": immutables, "Immutables": immutables,
} }
metrics2[c.name] = { metrics2[contract.name] = {
"Public": 0, "Public": 0,
"External": 0, "External": 0,
"Internal": 0, "Internal": 0,
"Private": 0, "Private": 0,
} }
metrics3[c.name] = { metrics3[contract.name] = {
"Mutating": 0, "Mutating": 0,
"View": 0, "View": 0,
"Pure": 0, "Pure": 0,
} }
metrics4[c.name] = { metrics4[contract.name] = {
"External mutating": 0, "External mutating": 0,
"No auth or onlyOwner": 0, "No auth or onlyOwner": 0,
"No modifiers": 0, "No modifiers": 0,
} }
metrics5[c.name] = { metrics5[contract.name] = {
"Ext calls": 0, "Ext calls": 0,
"RFC": 0, "RFC": 0,
"NOC": len(dependents[c.name]), "NOC": len(dependents[contract.name]),
"DIT": compute_dit(c), "DIT": compute_dit(contract),
"CBO": coupling[contract.name]["Dependents"] + coupling[contract.name]["Dependencies"],
} }
for func in c.functions: for func in contract.functions:
if func.name == "constructor": if func.name == "constructor":
continue continue
pure = func.pure pure = func.pure
@ -147,29 +152,33 @@ def compute_metrics(contracts):
# convert irs to string with target function and contract name # convert irs to string with target function and contract name
external_calls = [] external_calls = []
for h in high_level_calls: for high_level_call in high_level_calls:
if hasattr(h.destination, "name"): if hasattr(high_level_call.destination, "name"):
external_calls.append(f"{h.function_name}{h.destination.name}") external_calls.append(
f"{high_level_call.function_name}{high_level_call.destination.name}"
)
else: else:
external_calls.append(f"{h.function_name}{h.destination.type.type.name}") external_calls.append(
f"{high_level_call.function_name}{high_level_call.destination.type.type.name}"
)
rfc += len(set(external_calls)) rfc += len(set(external_calls))
metrics2[c.name]["Public"] += 1 if public else 0 metrics2[contract.name]["Public"] += 1 if public else 0
metrics2[c.name]["External"] += 1 if external else 0 metrics2[contract.name]["External"] += 1 if external else 0
metrics2[c.name]["Internal"] += 1 if internal else 0 metrics2[contract.name]["Internal"] += 1 if internal else 0
metrics2[c.name]["Private"] += 1 if private else 0 metrics2[contract.name]["Private"] += 1 if private else 0
metrics3[c.name]["Mutating"] += 1 if mutating else 0 metrics3[contract.name]["Mutating"] += 1 if mutating else 0
metrics3[c.name]["View"] += 1 if view else 0 metrics3[contract.name]["View"] += 1 if view else 0
metrics3[c.name]["Pure"] += 1 if pure else 0 metrics3[contract.name]["Pure"] += 1 if pure else 0
metrics4[c.name]["External mutating"] += 1 if external_public_mutating else 0 metrics4[contract.name]["External mutating"] += 1 if external_public_mutating else 0
metrics4[c.name]["No auth or onlyOwner"] += 1 if external_no_auth else 0 metrics4[contract.name]["No auth or onlyOwner"] += 1 if external_no_auth else 0
metrics4[c.name]["No modifiers"] += 1 if external_no_modifiers else 0 metrics4[contract.name]["No modifiers"] += 1 if external_no_modifiers else 0
metrics5[c.name]["Ext calls"] += len(external_calls) metrics5[contract.name]["Ext calls"] += len(external_calls)
metrics5[c.name]["RFC"] = rfc metrics5[contract.name]["RFC"] = rfc
return metrics1, metrics2, metrics3, metrics4, metrics5 return metrics1, metrics2, metrics3, metrics4, metrics5
@ -213,7 +222,7 @@ def no_auth(func) -> bool:
class CKMetrics(AbstractPrinter): class CKMetrics(AbstractPrinter):
ARGUMENT = "ck" ARGUMENT = "ck"
HELP = "Computes the CK complexity metrics for each contract" HELP = "Chidamber and Kemerer (CK) complexity metrics and related function attributes"
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#ck" WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#ck"
@ -246,8 +255,12 @@ class CKMetrics(AbstractPrinter):
table3 = make_pretty_table(["Contract", *keys], metrics4, True) table3 = make_pretty_table(["Contract", *keys], metrics4, True)
txt += str(table3) + "\n" txt += str(table3) + "\n"
# metrics5: ext calls and rfc # metrics5: ext calls and ck metrics
txt += bold("\nExt calls and RFC\n") txt += bold("\nExternal calls and CK Metrics:\n")
txt += bold("Response For a Class (RFC)\n")
txt += bold("Number of Children (NOC)\n")
txt += bold("Depth of Inheritance Tree (DIT)\n")
txt += bold("Coupling Between Object Classes (CBO)\n")
keys = list(metrics5[self.contracts[0].name].keys()) keys = list(metrics5[self.contracts[0].name].keys())
table4 = make_pretty_table(["Contract", *keys], metrics5, False) table4 = make_pretty_table(["Contract", *keys], metrics5, False)
txt += str(table4) + "\n" txt += str(table4) + "\n"

@ -78,25 +78,3 @@ def make_pretty_table_simple(
for k, v in data.items(): for k, v in data.items():
table.add_row([k] + [v]) table.add_row([k] + [v])
return table return table
# takes a dict of dicts and returns a dict of dicts with the keys transposed
# example:
# in:
# {
# "dep": {"loc": 0, "sloc": 0, "cloc": 0},
# "test": {"loc": 0, "sloc": 0, "cloc": 0},
# "src": {"loc": 0, "sloc": 0, "cloc": 0},
# }
# out:
# {
# 'loc': {'dep': 0, 'test': 0, 'src': 0},
# 'sloc': {'dep': 0, 'test': 0, 'src': 0},
# 'cloc': {'dep': 0, 'test': 0, 'src': 0},
# }
def transpose(table):
any_key = list(table.keys())[0]
return {
inner_key: {outer_key: table[outer_key][inner_key] for outer_key in table}
for inner_key in table[any_key]
}

Loading…
Cancel
Save