Skip to content

Commit

Permalink
feat: add CBO
Browse files Browse the repository at this point in the history
  • Loading branch information
devtooligan committed Jun 15, 2023
1 parent 314364e commit 14c9761
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 54 deletions.
1 change: 1 addition & 0 deletions slither/printers/all_printers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@
from .summary.when_not_paused import PrinterWhenNotPaused
from .summary.declaration import Declaration
from .functions.dominator import Dominator
from .summary.martin import Martin
77 changes: 45 additions & 32 deletions slither/printers/summary/ck.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@
- 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.
- 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:
- 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.
- Coupling Between Object Classes (CBO) is a metric that measures the number of classes a class is coupled to.
"""
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.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):
Expand Down Expand Up @@ -95,37 +96,41 @@ def compute_metrics(contracts):
for inherited in contracts
}

for c in contracts:
(state_variables, constants, immutables, public_getters) = count_variables(c)
# We pass 0 for the 2nd arg (abstractness) because we only care about the coupling metrics (Ca and Ce)
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
metrics1[c.name] = {
metrics1[contract.name] = {
"State variables": state_variables,
"Constants": constants,
"Immutables": immutables,
}
metrics2[c.name] = {
metrics2[contract.name] = {
"Public": 0,
"External": 0,
"Internal": 0,
"Private": 0,
}
metrics3[c.name] = {
metrics3[contract.name] = {
"Mutating": 0,
"View": 0,
"Pure": 0,
}
metrics4[c.name] = {
metrics4[contract.name] = {
"External mutating": 0,
"No auth or onlyOwner": 0,
"No modifiers": 0,
}
metrics5[c.name] = {
metrics5[contract.name] = {
"Ext calls": 0,
"RFC": 0,
"NOC": len(dependents[c.name]),
"DIT": compute_dit(c),
"NOC": len(dependents[contract.name]),
"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":
continue
pure = func.pure
Expand All @@ -147,29 +152,33 @@ def compute_metrics(contracts):

# convert irs to string with target function and contract name
external_calls = []
for h in high_level_calls:
if hasattr(h.destination, "name"):
external_calls.append(f"{h.function_name}{h.destination.name}")
for high_level_call in high_level_calls:
if hasattr(high_level_call.destination, "name"):
external_calls.append(
f"{high_level_call.function_name}{high_level_call.destination.name}"
)
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))

metrics2[c.name]["Public"] += 1 if public else 0
metrics2[c.name]["External"] += 1 if external else 0
metrics2[c.name]["Internal"] += 1 if internal else 0
metrics2[c.name]["Private"] += 1 if private else 0
metrics2[contract.name]["Public"] += 1 if public else 0
metrics2[contract.name]["External"] += 1 if external else 0
metrics2[contract.name]["Internal"] += 1 if internal else 0
metrics2[contract.name]["Private"] += 1 if private else 0

metrics3[c.name]["Mutating"] += 1 if mutating else 0
metrics3[c.name]["View"] += 1 if view else 0
metrics3[c.name]["Pure"] += 1 if pure else 0
metrics3[contract.name]["Mutating"] += 1 if mutating else 0
metrics3[contract.name]["View"] += 1 if view else 0
metrics3[contract.name]["Pure"] += 1 if pure else 0

metrics4[c.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[c.name]["No modifiers"] += 1 if external_no_modifiers else 0
metrics4[contract.name]["External mutating"] += 1 if external_public_mutating else 0
metrics4[contract.name]["No auth or onlyOwner"] += 1 if external_no_auth else 0
metrics4[contract.name]["No modifiers"] += 1 if external_no_modifiers else 0

metrics5[c.name]["Ext calls"] += len(external_calls)
metrics5[c.name]["RFC"] = rfc
metrics5[contract.name]["Ext calls"] += len(external_calls)
metrics5[contract.name]["RFC"] = rfc

return metrics1, metrics2, metrics3, metrics4, metrics5

Expand Down Expand Up @@ -213,7 +222,7 @@ def no_auth(func) -> bool:

class CKMetrics(AbstractPrinter):
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"

Expand Down Expand Up @@ -246,8 +255,12 @@ def output(self, _filename):
table3 = make_pretty_table(["Contract", *keys], metrics4, True)
txt += str(table3) + "\n"

# metrics5: ext calls and rfc
txt += bold("\nExt calls and RFC\n")
# metrics5: ext calls and ck metrics
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())
table4 = make_pretty_table(["Contract", *keys], metrics5, False)
txt += str(table4) + "\n"
Expand Down
22 changes: 0 additions & 22 deletions slither/utils/myprettytable.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,25 +78,3 @@ def make_pretty_table_simple(
for k, v in data.items():
table.add_row([k] + [v])
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]
}

0 comments on commit 14c9761

Please sign in to comment.