slither.utils.ck
CK Metrics are a suite of six software metrics proposed by Chidamber and Kemerer in 1994. These metrics are used to measure the complexity of a class. https://en.wikipedia.org/wiki/Programming_complexity
- 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.
During the calculation of the metrics above, there are a number of other intermediate metrics that are calculated. These are also included in the output:
- State variables: total number of state variables
- Constants: total number of constants
- Immutables: total number of immutables
- Public: total number of public functions
- External: total number of external functions
- Internal: total number of internal functions
- Private: total number of private functions
- Mutating: total number of state mutating functions
- View: total number of view functions
- Pure: total number of pure functions
- External mutating: total number of external mutating functions
- No auth or onlyOwner: total number of functions without auth or onlyOwner modifiers
- No modifiers: total number of functions without modifiers
- Ext calls: total number of external calls
1""" 2 CK Metrics are a suite of six software metrics proposed by Chidamber and Kemerer in 1994. 3 These metrics are used to measure the complexity of a class. 4 https://en.wikipedia.org/wiki/Programming_complexity 5 6 - Response For a Class (RFC) is a metric that measures the number of unique method calls within a class. 7 - Number of Children (NOC) is a metric that measures the number of children a class has. 8 - Depth of Inheritance Tree (DIT) is a metric that measures the number of parent classes a class has. 9 - Coupling Between Object Classes (CBO) is a metric that measures the number of classes a class is coupled to. 10 11 Not implemented: 12 - Lack of Cohesion of Methods (LCOM) is a metric that measures the lack of cohesion in methods. 13 - Weighted Methods per Class (WMC) is a metric that measures the complexity of a class. 14 15 During the calculation of the metrics above, there are a number of other intermediate metrics that are calculated. 16 These are also included in the output: 17 - State variables: total number of state variables 18 - Constants: total number of constants 19 - Immutables: total number of immutables 20 - Public: total number of public functions 21 - External: total number of external functions 22 - Internal: total number of internal functions 23 - Private: total number of private functions 24 - Mutating: total number of state mutating functions 25 - View: total number of view functions 26 - Pure: total number of pure functions 27 - External mutating: total number of external mutating functions 28 - No auth or onlyOwner: total number of functions without auth or onlyOwner modifiers 29 - No modifiers: total number of functions without modifiers 30 - Ext calls: total number of external calls 31 32""" 33from collections import OrderedDict 34from typing import Tuple, List, Dict 35from dataclasses import dataclass, field 36from slither.utils.colors import bold 37from slither.core.declarations import Contract 38from slither.utils.myprettytable import make_pretty_table, MyPrettyTable 39from slither.utils.martin import MartinMetrics 40from slither.slithir.operations.high_level_call import HighLevelCall 41 42 43# Utility functions 44 45 46def compute_dit(contract: Contract, depth: int = 0) -> int: 47 """ 48 Recursively compute the depth of inheritance tree (DIT) of a contract 49 Args: 50 contract(core.declarations.contract.Contract): contract to compute DIT for 51 depth(int): current depth of the contract 52 Returns: 53 int: depth of the contract 54 """ 55 if not contract.inheritance: 56 return depth 57 max_dit = depth 58 for inherited_contract in contract.inheritance: 59 dit = compute_dit(inherited_contract, depth + 1) 60 max_dit = max(max_dit, dit) 61 return max_dit 62 63 64def has_auth(func) -> bool: 65 """ 66 Check if a function has no auth or only_owner modifiers 67 Args: 68 func(core.declarations.function.Function): function to check 69 Returns: 70 bool True if it does have auth or only_owner modifiers 71 """ 72 for modifier in func.modifiers: 73 if "auth" in modifier.name or "only_owner" in modifier.name: 74 return True 75 return False 76 77 78# Utility classes for calculating CK metrics 79 80 81@dataclass 82# pylint: disable=too-many-instance-attributes 83class CKContractMetrics: 84 """Class to hold the CK metrics for a single contract.""" 85 86 contract: Contract 87 88 # Used to calculate CBO - should be passed in as a constructor arg 89 martin_metrics: Dict 90 91 # Used to calculate NOC 92 dependents: Dict 93 94 state_variables: int = 0 95 constants: int = 0 96 immutables: int = 0 97 public: int = 0 98 external: int = 0 99 internal: int = 0 100 private: int = 0 101 mutating: int = 0 102 view: int = 0 103 pure: int = 0 104 external_mutating: int = 0 105 no_auth_or_only_owner: int = 0 106 no_modifiers: int = 0 107 ext_calls: int = 0 108 rfc: int = 0 109 noc: int = 0 110 dit: int = 0 111 cbo: int = 0 112 113 def __post_init__(self) -> None: 114 if not hasattr(self.contract, "functions"): 115 return 116 self.count_variables() 117 self.noc = len(self.dependents[self.contract.name]) 118 self.dit = compute_dit(self.contract) 119 self.cbo = ( 120 self.martin_metrics[self.contract.name].ca + self.martin_metrics[self.contract.name].ce 121 ) 122 self.calculate_metrics() 123 124 # pylint: disable=too-many-locals 125 # pylint: disable=too-many-branches 126 def calculate_metrics(self) -> None: 127 """Calculate the metrics for a contract""" 128 rfc = self.public # initialize with public getter count 129 for func in self.contract.functions: 130 if func.name == "constructor": 131 continue 132 pure = func.pure 133 view = not pure and func.view 134 mutating = not pure and not view 135 external = func.visibility == "external" 136 public = func.visibility == "public" 137 internal = func.visibility == "internal" 138 private = func.visibility == "private" 139 external_public_mutating = external or public and mutating 140 external_no_auth = external_public_mutating and not has_auth(func) 141 external_no_modifiers = external_public_mutating and len(func.modifiers) == 0 142 if external or public: 143 rfc += 1 144 145 high_level_calls = [ 146 ir for node in func.nodes for ir in node.irs_ssa if isinstance(ir, HighLevelCall) 147 ] 148 149 # convert irs to string with target function and contract name 150 external_calls = [] 151 for high_level_call in high_level_calls: 152 if isinstance(high_level_call.destination, Contract): 153 destination_contract = high_level_call.destination.name 154 elif isinstance(high_level_call.destination, str): 155 destination_contract = high_level_call.destination 156 elif not hasattr(high_level_call.destination, "type"): 157 continue 158 elif isinstance(high_level_call.destination.type, Contract): 159 destination_contract = high_level_call.destination.type.name 160 elif isinstance(high_level_call.destination.type, str): 161 destination_contract = high_level_call.destination.type 162 elif not hasattr(high_level_call.destination.type, "type"): 163 continue 164 elif isinstance(high_level_call.destination.type.type, Contract): 165 destination_contract = high_level_call.destination.type.type.name 166 elif isinstance(high_level_call.destination.type.type, str): 167 destination_contract = high_level_call.destination.type.type 168 else: 169 continue 170 external_calls.append(f"{high_level_call.function_name}{destination_contract}") 171 rfc += len(set(external_calls)) 172 173 self.public += public 174 self.external += external 175 self.internal += internal 176 self.private += private 177 178 self.mutating += mutating 179 self.view += view 180 self.pure += pure 181 182 self.external_mutating += external_public_mutating 183 self.no_auth_or_only_owner += external_no_auth 184 self.no_modifiers += external_no_modifiers 185 186 self.ext_calls += len(external_calls) 187 self.rfc = rfc 188 189 def count_variables(self) -> None: 190 """Count the number of variables in a contract""" 191 state_variable_count = 0 192 constant_count = 0 193 immutable_count = 0 194 public_getter_count = 0 195 for variable in self.contract.variables: 196 if variable.is_constant: 197 constant_count += 1 198 elif variable.is_immutable: 199 immutable_count += 1 200 else: 201 state_variable_count += 1 202 if variable.visibility == "Public": 203 public_getter_count += 1 204 self.state_variables = state_variable_count 205 self.constants = constant_count 206 self.immutables = immutable_count 207 208 # initialize RFC with public getter count 209 # self.public is used count public functions not public variables 210 self.rfc = public_getter_count 211 212 def to_dict(self) -> Dict[str, float]: 213 """Return the metrics as a dictionary.""" 214 return OrderedDict( 215 { 216 "State variables": self.state_variables, 217 "Constants": self.constants, 218 "Immutables": self.immutables, 219 "Public": self.public, 220 "External": self.external, 221 "Internal": self.internal, 222 "Private": self.private, 223 "Mutating": self.mutating, 224 "View": self.view, 225 "Pure": self.pure, 226 "External mutating": self.external_mutating, 227 "No auth or onlyOwner": self.no_auth_or_only_owner, 228 "No modifiers": self.no_modifiers, 229 "Ext calls": self.ext_calls, 230 "RFC": self.rfc, 231 "NOC": self.noc, 232 "DIT": self.dit, 233 "CBO": self.cbo, 234 } 235 ) 236 237 238@dataclass 239class SectionInfo: 240 """Class to hold the information for a section of the report.""" 241 242 title: str 243 pretty_table: MyPrettyTable 244 txt: str 245 246 247@dataclass 248# pylint: disable=too-many-instance-attributes 249class CKMetrics: 250 """Class to hold the CK metrics for all contracts. Contains methods useful for reporting. 251 252 There are 5 sections in the report: 253 1. Variable count by type (state, constant, immutable) 254 2. Function count by visibility (public, external, internal, private) 255 3. Function count by mutability (mutating, view, pure) 256 4. External mutating function count by modifier (external mutating, no auth or onlyOwner, no modifiers) 257 5. CK metrics (RFC, NOC, DIT, CBO) 258 """ 259 260 contracts: List[Contract] = field(default_factory=list) 261 contract_metrics: OrderedDict = field(default_factory=OrderedDict) 262 title: str = "CK complexity metrics" 263 full_text: str = "" 264 auxiliary1: SectionInfo = field(default=SectionInfo) 265 auxiliary2: SectionInfo = field(default=SectionInfo) 266 auxiliary3: SectionInfo = field(default=SectionInfo) 267 auxiliary4: SectionInfo = field(default=SectionInfo) 268 core: SectionInfo = field(default=SectionInfo) 269 AUXILIARY1_KEYS = ( 270 "State variables", 271 "Constants", 272 "Immutables", 273 ) 274 AUXILIARY2_KEYS = ( 275 "Public", 276 "External", 277 "Internal", 278 "Private", 279 ) 280 AUXILIARY3_KEYS = ( 281 "Mutating", 282 "View", 283 "Pure", 284 ) 285 AUXILIARY4_KEYS = ( 286 "External mutating", 287 "No auth or onlyOwner", 288 "No modifiers", 289 ) 290 CORE_KEYS = ( 291 "Ext calls", 292 "RFC", 293 "NOC", 294 "DIT", 295 "CBO", 296 ) 297 SECTIONS: Tuple[Tuple[str, str, Tuple[str]]] = ( 298 ("Variables", "auxiliary1", AUXILIARY1_KEYS), 299 ("Function visibility", "auxiliary2", AUXILIARY2_KEYS), 300 ("State mutability", "auxiliary3", AUXILIARY3_KEYS), 301 ("External mutating functions", "auxiliary4", AUXILIARY4_KEYS), 302 ("Core", "core", CORE_KEYS), 303 ) 304 305 def __post_init__(self) -> None: 306 martin_metrics = MartinMetrics(self.contracts).contract_metrics 307 dependents = { 308 inherited.name: { 309 contract.name 310 for contract in self.contracts 311 if inherited.name in contract.inheritance 312 } 313 for inherited in self.contracts 314 } 315 for contract in self.contracts: 316 self.contract_metrics[contract.name] = CKContractMetrics( 317 contract=contract, martin_metrics=martin_metrics, dependents=dependents 318 ) 319 320 # Create the table and text for each section. 321 data = { 322 contract.name: self.contract_metrics[contract.name].to_dict() 323 for contract in self.contracts 324 } 325 326 subtitle = "" 327 # Update each section 328 for (title, attr, keys) in self.SECTIONS: 329 if attr == "core": 330 # Special handling for core section 331 totals_enabled = False 332 subtitle += bold("RFC: Response For a Class\n") 333 subtitle += bold("NOC: Number of Children\n") 334 subtitle += bold("DIT: Depth of Inheritance Tree\n") 335 subtitle += bold("CBO: Coupling Between Object Classes\n") 336 else: 337 totals_enabled = True 338 subtitle = "" 339 340 pretty_table = make_pretty_table(["Contract", *keys], data, totals=totals_enabled) 341 section_title = f"{self.title} ({title})" 342 txt = f"\n\n{section_title}:\n{subtitle}{pretty_table}\n" 343 self.full_text += txt 344 setattr( 345 self, 346 attr, 347 SectionInfo(title=section_title, pretty_table=pretty_table, txt=txt), 348 )
47def compute_dit(contract: Contract, depth: int = 0) -> int: 48 """ 49 Recursively compute the depth of inheritance tree (DIT) of a contract 50 Args: 51 contract(core.declarations.contract.Contract): contract to compute DIT for 52 depth(int): current depth of the contract 53 Returns: 54 int: depth of the contract 55 """ 56 if not contract.inheritance: 57 return depth 58 max_dit = depth 59 for inherited_contract in contract.inheritance: 60 dit = compute_dit(inherited_contract, depth + 1) 61 max_dit = max(max_dit, dit) 62 return max_dit
Recursively compute the depth of inheritance tree (DIT) of a contract Args: contract(core.declarations.contract.Contract): contract to compute DIT for depth(int): current depth of the contract Returns: int: depth of the contract
65def has_auth(func) -> bool: 66 """ 67 Check if a function has no auth or only_owner modifiers 68 Args: 69 func(core.declarations.function.Function): function to check 70 Returns: 71 bool True if it does have auth or only_owner modifiers 72 """ 73 for modifier in func.modifiers: 74 if "auth" in modifier.name or "only_owner" in modifier.name: 75 return True 76 return False
Check if a function has no auth or only_owner modifiers Args: func(core.declarations.function.Function): function to check Returns: bool True if it does have auth or only_owner modifiers
84class CKContractMetrics: 85 """Class to hold the CK metrics for a single contract.""" 86 87 contract: Contract 88 89 # Used to calculate CBO - should be passed in as a constructor arg 90 martin_metrics: Dict 91 92 # Used to calculate NOC 93 dependents: Dict 94 95 state_variables: int = 0 96 constants: int = 0 97 immutables: int = 0 98 public: int = 0 99 external: int = 0 100 internal: int = 0 101 private: int = 0 102 mutating: int = 0 103 view: int = 0 104 pure: int = 0 105 external_mutating: int = 0 106 no_auth_or_only_owner: int = 0 107 no_modifiers: int = 0 108 ext_calls: int = 0 109 rfc: int = 0 110 noc: int = 0 111 dit: int = 0 112 cbo: int = 0 113 114 def __post_init__(self) -> None: 115 if not hasattr(self.contract, "functions"): 116 return 117 self.count_variables() 118 self.noc = len(self.dependents[self.contract.name]) 119 self.dit = compute_dit(self.contract) 120 self.cbo = ( 121 self.martin_metrics[self.contract.name].ca + self.martin_metrics[self.contract.name].ce 122 ) 123 self.calculate_metrics() 124 125 # pylint: disable=too-many-locals 126 # pylint: disable=too-many-branches 127 def calculate_metrics(self) -> None: 128 """Calculate the metrics for a contract""" 129 rfc = self.public # initialize with public getter count 130 for func in self.contract.functions: 131 if func.name == "constructor": 132 continue 133 pure = func.pure 134 view = not pure and func.view 135 mutating = not pure and not view 136 external = func.visibility == "external" 137 public = func.visibility == "public" 138 internal = func.visibility == "internal" 139 private = func.visibility == "private" 140 external_public_mutating = external or public and mutating 141 external_no_auth = external_public_mutating and not has_auth(func) 142 external_no_modifiers = external_public_mutating and len(func.modifiers) == 0 143 if external or public: 144 rfc += 1 145 146 high_level_calls = [ 147 ir for node in func.nodes for ir in node.irs_ssa if isinstance(ir, HighLevelCall) 148 ] 149 150 # convert irs to string with target function and contract name 151 external_calls = [] 152 for high_level_call in high_level_calls: 153 if isinstance(high_level_call.destination, Contract): 154 destination_contract = high_level_call.destination.name 155 elif isinstance(high_level_call.destination, str): 156 destination_contract = high_level_call.destination 157 elif not hasattr(high_level_call.destination, "type"): 158 continue 159 elif isinstance(high_level_call.destination.type, Contract): 160 destination_contract = high_level_call.destination.type.name 161 elif isinstance(high_level_call.destination.type, str): 162 destination_contract = high_level_call.destination.type 163 elif not hasattr(high_level_call.destination.type, "type"): 164 continue 165 elif isinstance(high_level_call.destination.type.type, Contract): 166 destination_contract = high_level_call.destination.type.type.name 167 elif isinstance(high_level_call.destination.type.type, str): 168 destination_contract = high_level_call.destination.type.type 169 else: 170 continue 171 external_calls.append(f"{high_level_call.function_name}{destination_contract}") 172 rfc += len(set(external_calls)) 173 174 self.public += public 175 self.external += external 176 self.internal += internal 177 self.private += private 178 179 self.mutating += mutating 180 self.view += view 181 self.pure += pure 182 183 self.external_mutating += external_public_mutating 184 self.no_auth_or_only_owner += external_no_auth 185 self.no_modifiers += external_no_modifiers 186 187 self.ext_calls += len(external_calls) 188 self.rfc = rfc 189 190 def count_variables(self) -> None: 191 """Count the number of variables in a contract""" 192 state_variable_count = 0 193 constant_count = 0 194 immutable_count = 0 195 public_getter_count = 0 196 for variable in self.contract.variables: 197 if variable.is_constant: 198 constant_count += 1 199 elif variable.is_immutable: 200 immutable_count += 1 201 else: 202 state_variable_count += 1 203 if variable.visibility == "Public": 204 public_getter_count += 1 205 self.state_variables = state_variable_count 206 self.constants = constant_count 207 self.immutables = immutable_count 208 209 # initialize RFC with public getter count 210 # self.public is used count public functions not public variables 211 self.rfc = public_getter_count 212 213 def to_dict(self) -> Dict[str, float]: 214 """Return the metrics as a dictionary.""" 215 return OrderedDict( 216 { 217 "State variables": self.state_variables, 218 "Constants": self.constants, 219 "Immutables": self.immutables, 220 "Public": self.public, 221 "External": self.external, 222 "Internal": self.internal, 223 "Private": self.private, 224 "Mutating": self.mutating, 225 "View": self.view, 226 "Pure": self.pure, 227 "External mutating": self.external_mutating, 228 "No auth or onlyOwner": self.no_auth_or_only_owner, 229 "No modifiers": self.no_modifiers, 230 "Ext calls": self.ext_calls, 231 "RFC": self.rfc, 232 "NOC": self.noc, 233 "DIT": self.dit, 234 "CBO": self.cbo, 235 } 236 )
Class to hold the CK metrics for a single contract.
127 def calculate_metrics(self) -> None: 128 """Calculate the metrics for a contract""" 129 rfc = self.public # initialize with public getter count 130 for func in self.contract.functions: 131 if func.name == "constructor": 132 continue 133 pure = func.pure 134 view = not pure and func.view 135 mutating = not pure and not view 136 external = func.visibility == "external" 137 public = func.visibility == "public" 138 internal = func.visibility == "internal" 139 private = func.visibility == "private" 140 external_public_mutating = external or public and mutating 141 external_no_auth = external_public_mutating and not has_auth(func) 142 external_no_modifiers = external_public_mutating and len(func.modifiers) == 0 143 if external or public: 144 rfc += 1 145 146 high_level_calls = [ 147 ir for node in func.nodes for ir in node.irs_ssa if isinstance(ir, HighLevelCall) 148 ] 149 150 # convert irs to string with target function and contract name 151 external_calls = [] 152 for high_level_call in high_level_calls: 153 if isinstance(high_level_call.destination, Contract): 154 destination_contract = high_level_call.destination.name 155 elif isinstance(high_level_call.destination, str): 156 destination_contract = high_level_call.destination 157 elif not hasattr(high_level_call.destination, "type"): 158 continue 159 elif isinstance(high_level_call.destination.type, Contract): 160 destination_contract = high_level_call.destination.type.name 161 elif isinstance(high_level_call.destination.type, str): 162 destination_contract = high_level_call.destination.type 163 elif not hasattr(high_level_call.destination.type, "type"): 164 continue 165 elif isinstance(high_level_call.destination.type.type, Contract): 166 destination_contract = high_level_call.destination.type.type.name 167 elif isinstance(high_level_call.destination.type.type, str): 168 destination_contract = high_level_call.destination.type.type 169 else: 170 continue 171 external_calls.append(f"{high_level_call.function_name}{destination_contract}") 172 rfc += len(set(external_calls)) 173 174 self.public += public 175 self.external += external 176 self.internal += internal 177 self.private += private 178 179 self.mutating += mutating 180 self.view += view 181 self.pure += pure 182 183 self.external_mutating += external_public_mutating 184 self.no_auth_or_only_owner += external_no_auth 185 self.no_modifiers += external_no_modifiers 186 187 self.ext_calls += len(external_calls) 188 self.rfc = rfc
Calculate the metrics for a contract
190 def count_variables(self) -> None: 191 """Count the number of variables in a contract""" 192 state_variable_count = 0 193 constant_count = 0 194 immutable_count = 0 195 public_getter_count = 0 196 for variable in self.contract.variables: 197 if variable.is_constant: 198 constant_count += 1 199 elif variable.is_immutable: 200 immutable_count += 1 201 else: 202 state_variable_count += 1 203 if variable.visibility == "Public": 204 public_getter_count += 1 205 self.state_variables = state_variable_count 206 self.constants = constant_count 207 self.immutables = immutable_count 208 209 # initialize RFC with public getter count 210 # self.public is used count public functions not public variables 211 self.rfc = public_getter_count
Count the number of variables in a contract
213 def to_dict(self) -> Dict[str, float]: 214 """Return the metrics as a dictionary.""" 215 return OrderedDict( 216 { 217 "State variables": self.state_variables, 218 "Constants": self.constants, 219 "Immutables": self.immutables, 220 "Public": self.public, 221 "External": self.external, 222 "Internal": self.internal, 223 "Private": self.private, 224 "Mutating": self.mutating, 225 "View": self.view, 226 "Pure": self.pure, 227 "External mutating": self.external_mutating, 228 "No auth or onlyOwner": self.no_auth_or_only_owner, 229 "No modifiers": self.no_modifiers, 230 "Ext calls": self.ext_calls, 231 "RFC": self.rfc, 232 "NOC": self.noc, 233 "DIT": self.dit, 234 "CBO": self.cbo, 235 } 236 )
Return the metrics as a dictionary.
240class SectionInfo: 241 """Class to hold the information for a section of the report.""" 242 243 title: str 244 pretty_table: MyPrettyTable 245 txt: str
Class to hold the information for a section of the report.
250class CKMetrics: 251 """Class to hold the CK metrics for all contracts. Contains methods useful for reporting. 252 253 There are 5 sections in the report: 254 1. Variable count by type (state, constant, immutable) 255 2. Function count by visibility (public, external, internal, private) 256 3. Function count by mutability (mutating, view, pure) 257 4. External mutating function count by modifier (external mutating, no auth or onlyOwner, no modifiers) 258 5. CK metrics (RFC, NOC, DIT, CBO) 259 """ 260 261 contracts: List[Contract] = field(default_factory=list) 262 contract_metrics: OrderedDict = field(default_factory=OrderedDict) 263 title: str = "CK complexity metrics" 264 full_text: str = "" 265 auxiliary1: SectionInfo = field(default=SectionInfo) 266 auxiliary2: SectionInfo = field(default=SectionInfo) 267 auxiliary3: SectionInfo = field(default=SectionInfo) 268 auxiliary4: SectionInfo = field(default=SectionInfo) 269 core: SectionInfo = field(default=SectionInfo) 270 AUXILIARY1_KEYS = ( 271 "State variables", 272 "Constants", 273 "Immutables", 274 ) 275 AUXILIARY2_KEYS = ( 276 "Public", 277 "External", 278 "Internal", 279 "Private", 280 ) 281 AUXILIARY3_KEYS = ( 282 "Mutating", 283 "View", 284 "Pure", 285 ) 286 AUXILIARY4_KEYS = ( 287 "External mutating", 288 "No auth or onlyOwner", 289 "No modifiers", 290 ) 291 CORE_KEYS = ( 292 "Ext calls", 293 "RFC", 294 "NOC", 295 "DIT", 296 "CBO", 297 ) 298 SECTIONS: Tuple[Tuple[str, str, Tuple[str]]] = ( 299 ("Variables", "auxiliary1", AUXILIARY1_KEYS), 300 ("Function visibility", "auxiliary2", AUXILIARY2_KEYS), 301 ("State mutability", "auxiliary3", AUXILIARY3_KEYS), 302 ("External mutating functions", "auxiliary4", AUXILIARY4_KEYS), 303 ("Core", "core", CORE_KEYS), 304 ) 305 306 def __post_init__(self) -> None: 307 martin_metrics = MartinMetrics(self.contracts).contract_metrics 308 dependents = { 309 inherited.name: { 310 contract.name 311 for contract in self.contracts 312 if inherited.name in contract.inheritance 313 } 314 for inherited in self.contracts 315 } 316 for contract in self.contracts: 317 self.contract_metrics[contract.name] = CKContractMetrics( 318 contract=contract, martin_metrics=martin_metrics, dependents=dependents 319 ) 320 321 # Create the table and text for each section. 322 data = { 323 contract.name: self.contract_metrics[contract.name].to_dict() 324 for contract in self.contracts 325 } 326 327 subtitle = "" 328 # Update each section 329 for (title, attr, keys) in self.SECTIONS: 330 if attr == "core": 331 # Special handling for core section 332 totals_enabled = False 333 subtitle += bold("RFC: Response For a Class\n") 334 subtitle += bold("NOC: Number of Children\n") 335 subtitle += bold("DIT: Depth of Inheritance Tree\n") 336 subtitle += bold("CBO: Coupling Between Object Classes\n") 337 else: 338 totals_enabled = True 339 subtitle = "" 340 341 pretty_table = make_pretty_table(["Contract", *keys], data, totals=totals_enabled) 342 section_title = f"{self.title} ({title})" 343 txt = f"\n\n{section_title}:\n{subtitle}{pretty_table}\n" 344 self.full_text += txt 345 setattr( 346 self, 347 attr, 348 SectionInfo(title=section_title, pretty_table=pretty_table, txt=txt), 349 )
Class to hold the CK metrics for all contracts. Contains methods useful for reporting.
There are 5 sections in the report:
- Variable count by type (state, constant, immutable)
- Function count by visibility (public, external, internal, private)
- Function count by mutability (mutating, view, pure)
- External mutating function count by modifier (external mutating, no auth or onlyOwner, no modifiers)
- CK metrics (RFC, NOC, DIT, CBO)