mirror of https://github.com/crytic/slither
commit
4ee0434a84
@ -0,0 +1,114 @@ |
||||
from slither.core.declarations.solidity_variables import (SolidityFunction, |
||||
SolidityVariableComposed) |
||||
from slither.detectors.abstract_detector import (AbstractDetector, |
||||
DetectorClassification) |
||||
from slither.slithir.operations import (HighLevelCall, |
||||
LowLevelCall, |
||||
LibraryCall) |
||||
from slither.utils.code_complexity import compute_cyclomatic_complexity |
||||
|
||||
|
||||
class ComplexFunction(AbstractDetector): |
||||
""" |
||||
Module detecting complex functions |
||||
A complex function is defined by: |
||||
- high cyclomatic complexity |
||||
- numerous writes to state variables |
||||
- numerous external calls |
||||
""" |
||||
|
||||
|
||||
ARGUMENT = 'complex-function' |
||||
HELP = 'Complex functions' |
||||
IMPACT = DetectorClassification.INFORMATIONAL |
||||
CONFIDENCE = DetectorClassification.MEDIUM |
||||
|
||||
MAX_STATE_VARIABLES = 10 |
||||
MAX_EXTERNAL_CALLS = 5 |
||||
MAX_CYCLOMATIC_COMPLEXITY = 7 |
||||
|
||||
CAUSE_CYCLOMATIC = "cyclomatic" |
||||
CAUSE_EXTERNAL_CALL = "external_calls" |
||||
CAUSE_STATE_VARS = "state_vars" |
||||
|
||||
|
||||
@staticmethod |
||||
def detect_complex_func(func): |
||||
"""Detect the cyclomatic complexity of the contract functions |
||||
shouldn't be greater than 7 |
||||
""" |
||||
result = [] |
||||
code_complexity = compute_cyclomatic_complexity(func) |
||||
|
||||
if code_complexity > ComplexFunction.MAX_CYCLOMATIC_COMPLEXITY: |
||||
result.append({ |
||||
"func": func, |
||||
"cause": ComplexFunction.CAUSE_CYCLOMATIC |
||||
}) |
||||
|
||||
"""Detect the number of external calls in the func |
||||
shouldn't be greater than 5 |
||||
""" |
||||
count = 0 |
||||
for node in func.nodes: |
||||
for ir in node.irs: |
||||
if isinstance(ir, (HighLevelCall, LowLevelCall, LibraryCall)): |
||||
count += 1 |
||||
|
||||
if count > ComplexFunction.MAX_EXTERNAL_CALLS: |
||||
result.append({ |
||||
"func": func, |
||||
"cause": ComplexFunction.CAUSE_EXTERNAL_CALL |
||||
}) |
||||
|
||||
"""Checks the number of the state variables written |
||||
shouldn't be greater than 10 |
||||
""" |
||||
if len(func.state_variables_written) > ComplexFunction.MAX_STATE_VARIABLES: |
||||
result.append({ |
||||
"func": func, |
||||
"cause": ComplexFunction.CAUSE_STATE_VARS |
||||
}) |
||||
|
||||
return result |
||||
|
||||
def detect_complex(self, contract): |
||||
ret = [] |
||||
|
||||
for func in contract.all_functions_called: |
||||
result = self.detect_complex_func(func) |
||||
ret.extend(result) |
||||
|
||||
return ret |
||||
|
||||
def detect(self): |
||||
results = [] |
||||
|
||||
for contract in self.contracts: |
||||
issues = self.detect_complex(contract) |
||||
|
||||
for issue in issues: |
||||
func, cause = issue.values() |
||||
func_name = func.name |
||||
|
||||
txt = "Complex function in {} Contract: {}, Function: {}" |
||||
|
||||
if cause == self.CAUSE_EXTERNAL_CALL: |
||||
txt += ", Reason: High number of external calls" |
||||
if cause == self.CAUSE_CYCLOMATIC: |
||||
txt += ", Reason: High number of branches" |
||||
if cause == self.CAUSE_STATE_VARS: |
||||
txt += ", Reason: High number of modified state variables" |
||||
|
||||
info = txt.format(self.filename, |
||||
contract.name, |
||||
func_name) |
||||
self.log(info) |
||||
|
||||
results.append({'vuln': 'ComplexFunc', |
||||
'sourceMapping': func.source_mapping, |
||||
'filename': self.filename, |
||||
'contract': contract.name, |
||||
'func': func_name}) |
||||
return results |
||||
|
@ -0,0 +1,75 @@ |
||||
# Funciton computing the code complexity |
||||
|
||||
def compute_number_edges(function): |
||||
""" |
||||
Compute the number of edges of the CFG |
||||
Args: |
||||
function (core.declarations.function.Function) |
||||
Returns: |
||||
int |
||||
""" |
||||
n = 0 |
||||
for node in function.nodes: |
||||
n += len(node.sons) |
||||
return n |
||||
|
||||
|
||||
def compute_strongly_connected_components(function): |
||||
""" |
||||
Compute strongly connected components |
||||
Based on Kosaraju algo |
||||
Implem follows wikipedia algo: https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm#The_algorithm |
||||
Args: |
||||
function (core.declarations.function.Function) |
||||
Returns: |
||||
list(list(nodes)) |
||||
""" |
||||
visited = {n:False for n in function.nodes} |
||||
assigned = {n:False for n in function.nodes} |
||||
components = [] |
||||
l = [] |
||||
|
||||
def visit(node): |
||||
if not visited[node]: |
||||
visited[node] = True |
||||
for son in node.sons: |
||||
visit(son) |
||||
l.append(node) |
||||
|
||||
for n in function.nodes: |
||||
visit(n) |
||||
|
||||
def assign(node, root): |
||||
if not assigned[node]: |
||||
assigned[node] = True |
||||
root.append(node) |
||||
for father in node.fathers: |
||||
assign(father, root) |
||||
|
||||
for n in l: |
||||
component = [] |
||||
assign(n, component) |
||||
if component: |
||||
components.append(component) |
||||
|
||||
return components |
||||
|
||||
def compute_cyclomatic_complexity(function): |
||||
""" |
||||
Compute the cyclomatic complexity of a function |
||||
Args: |
||||
function (core.declarations.function.Function) |
||||
Returns: |
||||
int |
||||
""" |
||||
# from https://en.wikipedia.org/wiki/Cyclomatic_complexity |
||||
# M = E - N + 2P |
||||
# where M is the complexity |
||||
# E number of edges |
||||
# N number of nodes |
||||
# P number of connected components |
||||
|
||||
E = compute_number_edges(function) |
||||
N = len(function.nodes) |
||||
P = len(compute_strongly_connected_components(function)) |
||||
return E - N + 2 * P |
@ -0,0 +1,88 @@ |
||||
pragma solidity ^0.4.24; |
||||
|
||||
contract Complex { |
||||
int numberOfSides = 7; |
||||
string shape; |
||||
uint i0 = 0; |
||||
uint i1 = 0; |
||||
uint i2 = 0; |
||||
uint i3 = 0; |
||||
uint i4 = 0; |
||||
uint i5 = 0; |
||||
uint i6 = 0; |
||||
uint i7 = 0; |
||||
uint i8 = 0; |
||||
uint i9 = 0; |
||||
uint i10 = 0; |
||||
|
||||
|
||||
function computeShape() external { |
||||
if (numberOfSides <= 2) { |
||||
shape = "Cant be a shape!"; |
||||
} else if (numberOfSides == 3) { |
||||
shape = "Triangle"; |
||||
} else if (numberOfSides == 4) { |
||||
shape = "Square"; |
||||
} else if (numberOfSides == 5) { |
||||
shape = "Pentagon"; |
||||
} else if (numberOfSides == 6) { |
||||
shape = "Hexagon"; |
||||
} else if (numberOfSides == 7) { |
||||
shape = "Heptagon"; |
||||
} else if (numberOfSides == 8) { |
||||
shape = "Octagon"; |
||||
} else if (numberOfSides == 9) { |
||||
shape = "Nonagon"; |
||||
} else if (numberOfSides == 10) { |
||||
shape = "Decagon"; |
||||
} else if (numberOfSides == 11) { |
||||
shape = "Hendecagon"; |
||||
} else { |
||||
shape = "Your shape is more than 11 sides."; |
||||
} |
||||
} |
||||
|
||||
function complexExternalWrites() external { |
||||
Increment test1 = new Increment(); |
||||
test1.increaseBy1(); |
||||
test1.increaseBy1(); |
||||
test1.increaseBy1(); |
||||
test1.increaseBy1(); |
||||
test1.increaseBy1(); |
||||
|
||||
Increment test2 = new Increment(); |
||||
test2.increaseBy1(); |
||||
|
||||
address test3 = new Increment(); |
||||
test3.call(bytes4(keccak256("increaseBy2()"))); |
||||
|
||||
address test4 = new Increment(); |
||||
test4.call(bytes4(keccak256("increaseBy2()"))); |
||||
} |
||||
|
||||
function complexStateVars() external { |
||||
i0 = 1; |
||||
i1 = 1; |
||||
i2 = 1; |
||||
i3 = 1; |
||||
i4 = 1; |
||||
i5 = 1; |
||||
i6 = 1; |
||||
i7 = 1; |
||||
i8 = 1; |
||||
i9 = 1; |
||||
i10 = 1; |
||||
} |
||||
} |
||||
|
||||
contract Increment { |
||||
uint i = 0; |
||||
|
||||
function increaseBy1() public { |
||||
i += 1; |
||||
} |
||||
|
||||
function increaseBy2() public { |
||||
i += 2; |
||||
} |
||||
} |
Loading…
Reference in new issue