Create utils.output.Output class to handle json output

Remove add*_json function
pull/362/head
Josselin 5 years ago
parent d77fcfe6b6
commit f70e89bf12
  1. 2
      plugin_example/slither_my_plugin/detectors/example.py
  2. 4
      slither/__main__.py
  3. 72
      slither/detectors/abstract_detector.py
  4. 8
      slither/detectors/attributes/const_functions.py
  5. 4
      slither/detectors/attributes/constant_pragma.py
  6. 2
      slither/detectors/attributes/incorrect_solc.py
  7. 2
      slither/detectors/attributes/locked_ether.py
  8. 2
      slither/detectors/erc/incorrect_erc20_interface.py
  9. 4
      slither/detectors/erc/incorrect_erc721_interface.py
  10. 8
      slither/detectors/erc/unindexed_event_parameters.py
  11. 4
      slither/detectors/examples/backdoor.py
  12. 4
      slither/detectors/functions/arbitrary_send.py
  13. 7
      slither/detectors/functions/complex_function.py
  14. 4
      slither/detectors/functions/external_function.py
  15. 4
      slither/detectors/functions/suicidal.py
  16. 60
      slither/detectors/naming_convention/naming_convention.py
  17. 4
      slither/detectors/operations/block_timestamp.py
  18. 4
      slither/detectors/operations/low_level_calls.py
  19. 4
      slither/detectors/operations/unused_return_values.py
  20. 4
      slither/detectors/operations/void_constructor.py
  21. 12
      slither/detectors/reentrancy/reentrancy_benign.py
  22. 12
      slither/detectors/reentrancy/reentrancy_eth.py
  23. 10
      slither/detectors/reentrancy/reentrancy_read_before_write.py
  24. 4
      slither/detectors/shadowing/abstract.py
  25. 4
      slither/detectors/shadowing/builtin_symbols.py
  26. 4
      slither/detectors/shadowing/local.py
  27. 5
      slither/detectors/shadowing/state.py
  28. 11
      slither/detectors/source/rtlo.py
  29. 4
      slither/detectors/statements/assembly.py
  30. 4
      slither/detectors/statements/calls_in_loop.py
  31. 4
      slither/detectors/statements/controlled_delegatecall.py
  32. 4
      slither/detectors/statements/deprecated_calls.py
  33. 4
      slither/detectors/statements/incorrect_strict_equality.py
  34. 4
      slither/detectors/statements/too_many_digits.py
  35. 5
      slither/detectors/statements/tx_origin.py
  36. 2
      slither/detectors/variables/possible_const_state_variables.py
  37. 2
      slither/detectors/variables/uninitialized_local_variables.py
  38. 2
      slither/detectors/variables/uninitialized_state_variables.py
  39. 2
      slither/detectors/variables/uninitialized_storage_variables.py
  40. 2
      slither/detectors/variables/unused_state_variables.py
  41. 32
      slither/printers/abstract_printer.py
  42. 6
      slither/printers/call/call_graph.py
  43. 6
      slither/printers/functions/authorization.py
  44. 6
      slither/printers/functions/cfg.py
  45. 4
      slither/printers/guidance/echidna.py
  46. 4
      slither/printers/inheritance/inheritance.py
  47. 6
      slither/printers/inheritance/inheritance_graph.py
  48. 81
      slither/printers/summary/constructor_calls.py
  49. 15
      slither/printers/summary/contract.py
  50. 6
      slither/printers/summary/data_depenency.py
  51. 6
      slither/printers/summary/function.py
  52. 6
      slither/printers/summary/function_ids.py
  53. 9
      slither/printers/summary/human_summary.py
  54. 6
      slither/printers/summary/modifier_calls.py
  55. 6
      slither/printers/summary/require_calls.py
  56. 32
      slither/printers/summary/slithir.py
  57. 22
      slither/printers/summary/slithir_ssa.py
  58. 7
      slither/printers/summary/variable_order.py
  59. 2
      slither/slither.py
  60. 6
      slither/tools/erc_conformance/__main__.py
  61. 8
      slither/tools/erc_conformance/erc/erc20.py
  62. 57
      slither/tools/erc_conformance/erc/ercs.py
  63. 37
      slither/tools/kspec_coverage/analysis.py
  64. 12
      slither/tools/upgradeability/__main__.py
  65. 28
      slither/tools/upgradeability/check_initialization.py
  66. 8
      slither/tools/upgradeability/check_variable_initialization.py
  67. 32
      slither/tools/upgradeability/compare_function_ids.py
  68. 22
      slither/tools/upgradeability/compare_variables_order.py
  69. 26
      slither/tools/upgradeability/constant_checks.py
  70. 452
      slither/utils/json_utils.py
  71. 454
      slither/utils/output.py

@ -23,6 +23,6 @@ class Example(AbstractDetector):
info = 'This is an example!'
json = self.generate_json_result(info)
json = self.generate_result(info)
return [json]

@ -19,7 +19,7 @@ from slither.detectors.abstract_detector import (AbstractDetector,
from slither.printers import all_printers
from slither.printers.abstract_printer import AbstractPrinter
from slither.slither import Slither
from slither.utils.json_utils import output_json
from slither.utils.output import output_to_json
from slither.utils.output_capture import StandardOutputCapture
from slither.utils.colors import red, yellow, set_colorization_enabled
from slither.utils.command_line import (output_detectors, output_results_to_markdown,
@ -638,7 +638,7 @@ def main_impl(all_detector_classes, all_printer_classes):
'stderr': StandardOutputCapture.get_stderr_output()
}
StandardOutputCapture.disable()
output_json(None if outputting_json_stdout else args.json, output_error, json_results)
output_to_json(None if outputting_json_stdout else args.json, output_error, json_results)
# Exit with the appropriate status code
if output_error:

@ -4,7 +4,7 @@ import re
from slither.utils.colors import green, yellow, red
from slither.formatters.exceptions import FormatImpossible
from slither.formatters.utils.patches import apply_patch, create_diff
from slither.utils import json_utils
from slither.utils.output import Output
class IncorrectDetectorInitialization(Exception):
@ -103,10 +103,12 @@ class AbstractDetector(metaclass=abc.ABCMeta):
@abc.abstractmethod
def _detect(self):
"""TODO Documentation"""
return
return []
def detect(self):
all_results = self._detect()
# Keep only dictionaries
all_results = [r.data for r in all_results]
results = []
# only keep valid result, and remove dupplicate
[results.append(r) for r in all_results if self.slither.valid_result(r) and r not in results]
@ -170,65 +172,17 @@ class AbstractDetector(metaclass=abc.ABCMeta):
def color(self):
return classification_colors[self.IMPACT]
def generate_json_result(self, info, additional_fields=None):
d = json_utils.generate_json_result(info,
additional_fields,
standard_format=self.STANDARD_JSON,
markdown_root=self.slither.markdown_root)
d['check'] = self.ARGUMENT
d['impact'] = classification_txt[self.IMPACT]
d['confidence'] = classification_txt[self.CONFIDENCE]
def generate_result(self, info, additional_fields=None):
output = Output(info,
additional_fields,
standard_format=self.STANDARD_JSON,
markdown_root=self.slither.markdown_root)
return d
@staticmethod
def add_variable_to_json(e, d, additional_fields=None):
json_utils.add_variable_to_json(e, d, additional_fields=additional_fields)
output.data['check'] = self.ARGUMENT
output.data['impact'] = classification_txt[self.IMPACT]
output.data['confidence'] = classification_txt[self.CONFIDENCE]
@staticmethod
def add_variables_to_json(e, d):
json_utils.add_variables_to_json(e, d)
@staticmethod
def add_contract_to_json(e, d, additional_fields=None):
json_utils.add_contract_to_json(e, d, additional_fields=additional_fields)
@staticmethod
def add_function_to_json(e, d, additional_fields=None):
json_utils.add_function_to_json(e, d, additional_fields=additional_fields)
@staticmethod
def add_functions_to_json(e, d, additional_fields=None):
json_utils.add_functions_to_json(e, d, additional_fields=additional_fields)
@staticmethod
def add_enum_to_json(e, d, additional_fields=None):
json_utils.add_enum_to_json(e, d, additional_fields=additional_fields)
@staticmethod
def add_struct_to_json(e, d, additional_fields=None):
json_utils.add_struct_to_json(e, d, additional_fields=additional_fields)
@staticmethod
def add_event_to_json(e, d, additional_fields=None):
json_utils.add_event_to_json(e, d, additional_fields=additional_fields)
@staticmethod
def add_pragma_to_json(e, d, additional_fields=None):
json_utils.add_pragma_to_json(e, d, additional_fields=additional_fields)
@staticmethod
def add_node_to_json(e, d, additional_fields=None):
json_utils.add_node_to_json(e, d, additional_fields=additional_fields)
@staticmethod
def add_nodes_to_json(e, d):
json_utils.add_nodes_to_json(e, d)
@staticmethod
def add_other_to_json(name, source_mapping, d, slither, additional_fields=None):
json_utils.add_other_to_json(name, source_mapping, d, slither, additional_fields=additional_fields)
return output
@staticmethod
def _format(slither, result):

@ -59,9 +59,9 @@ All the calls to `get` revert, breaking Bob's smart contract execution.'''
attr = 'view' if f.view else 'pure'
info = [f, f' is declared {attr} but contains assembly code\n']
json = self.generate_json_result(info, {'contains_assembly': True})
res = self.generate_result(info, {'contains_assembly': True})
results.append(json)
results.append(res)
variables_written = f.all_state_variables_written()
if variables_written:
@ -72,9 +72,9 @@ All the calls to `get` revert, breaking Bob's smart contract execution.'''
for variable_written in variables_written:
info += ['\t- ', variable_written, '\n']
json = self.generate_json_result(info, {'contains_assembly': False})
res = self.generate_result(info, {'contains_assembly': False})
results.append(json)
results.append(res)
return results

@ -36,9 +36,9 @@ class ConstantPragma(AbstractDetector):
for p in pragma:
info += ["\t- ", p, "\n"]
json = self.generate_json_result(info)
res = self.generate_result(info)
results.append(json)
results.append(res)
return results

@ -102,7 +102,7 @@ Use Solidity 0.4.25 or 0.5.3. Consider using the latest version of Solidity for
for (reason, p) in disallowed_pragmas:
info = ["Pragma version", p, f" {reason}\n"]
json = self.generate_json_result(info)
json = self.generate_result(info)
results.append(json)

@ -80,7 +80,7 @@ Every ether sent to `Locked` will be lost.'''
info += [f"\t - ", function, "\n"]
info += "\tBut does not have a function to withdraw the ether\n"
json = self.generate_json_result(info)
json = self.generate_result(info)
results.append(json)

@ -88,7 +88,7 @@ contract Token{
if functions:
for function in functions:
info = [c, " has incorrect ERC20 function interface:", function, "\n"]
json = self.generate_json_result(info)
json = self.generate_result(info)
results.append(json)

@ -87,8 +87,8 @@ contract Token{
if functions:
for function in functions:
info = [c, " has incorrect ERC721 function interface:", function, "\n"]
json = self.generate_json_result(info)
res = self.generate_result(info)
results.append(json)
results.append(res)
return results

@ -76,12 +76,10 @@ In this case, Transfer and Approval events should have the 'indexed' keyword on
info = ["ERC20 event ", event, f"does not index parameter {parameter}\n"]
# Add the events to the JSON (note: we do not add the params/vars as they have no source mapping).
json = self.generate_json_result(info)
res = self.generate_result(info)
self.add_event_to_json(event, json, {
"parameter_name": parameter.name
})
results.append(json)
res.add(event, {"parameter_name": parameter.name})
results.append(res)
return results

@ -29,8 +29,8 @@ class Backdoor(AbstractDetector):
info = ['Backdoor function found in ', f, '\n']
# Add the result in result
json = self.generate_json_result(info)
res = self.generate_result(info)
results.append(json)
results.append(res)
return results

@ -114,8 +114,8 @@ Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract
for node in nodes:
info += ['\t- ', node, '\n']
json = self.generate_json_result(info)
res = self.generate_result(info)
results.append(json)
results.append(res)
return results

@ -31,6 +31,7 @@ class ComplexFunction(AbstractDetector):
CAUSE_EXTERNAL_CALL = "external_calls"
CAUSE_STATE_VARS = "state_vars"
STANDARD_JSON = True
@staticmethod
def detect_complex_func(func):
@ -104,14 +105,14 @@ class ComplexFunction(AbstractDetector):
info = info + "\n"
self.log(info)
json = self.generate_json_result(info)
self.add_function_to_json(func, json, {
res = self.generate_result(info)
res.add(func, {
'high_number_of_external_calls': cause == self.CAUSE_EXTERNAL_CALL,
'high_number_of_branches': cause == self.CAUSE_CYCLOMATIC,
'high_number_of_state_variables': cause == self.CAUSE_STATE_VARS
})
results.append(json)
results.append(res)
return results

@ -187,9 +187,9 @@ class ExternalFunction(AbstractDetector):
for other_function_definition in all_function_definitions:
info += [f"\t- ", other_function_definition, "\n"]
json = self.generate_json_result(info)
res = self.generate_result(info)
results.append(json)
results.append(res)
return results

@ -75,8 +75,8 @@ Bob calls `kill` and destructs the contract.'''
info = [func, " allows anyone to destruct the contract\n"]
json = self.generate_json_result(info)
res = self.generate_result(info)
results.append(json)
results.append(res)
return results

@ -62,34 +62,34 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
if not self.is_cap_words(contract.name):
info = ["Contract ", contract, " is not in CapWords\n"]
json = self.generate_json_result(info)
self.add_contract_to_json(contract, json, {
res = self.generate_result(info)
res.add(contract, {
"target": "contract",
"convention": "CapWords"
})
results.append(json)
results.append(res)
for struct in contract.structures_declared:
if not self.is_cap_words(struct.name):
info = ["Struct ", struct, " is not in CapWords\n"]
json = self.generate_json_result(info)
self.add_struct_to_json(struct, json, {
res = self.generate_result(info)
res.add(struct, {
"target": "structure",
"convention": "CapWords"
})
results.append(json)
results.append(res)
for event in contract.events_declared:
if not self.is_cap_words(event.name):
info = ["Event ", event, " is not in CapWords\n"]
json = self.generate_json_result(info)
self.add_event_to_json(event, json, {
res = self.generate_result(info)
res.add(event, {
"target": "event",
"convention": "CapWords"
})
results.append(json)
results.append(res)
for func in contract.functions_declared:
if func.is_constructor:
@ -101,12 +101,12 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
continue
info = ["Function ", func, " is not in mixedCase\n"]
json = self.generate_json_result(info)
self.add_function_to_json(func, json, {
res = self.generate_result(info)
res.add(func, {
"target": "function",
"convention": "mixedCase"
})
results.append(json)
results.append(res)
for argument in func.parameters:
# Ignore parameter names that are not specified i.e. empty strings
@ -119,24 +119,24 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
if not correct_naming:
info = ["Parameter ", argument, " is not in mixedCase\n"]
json = self.generate_json_result(info)
self.add_variable_to_json(argument, json, {
res = self.generate_result(info)
res.add(argument, {
"target": "parameter",
"convention": "mixedCase"
})
results.append(json)
results.append(res)
for var in contract.state_variables_declared:
if self.should_avoid_name(var.name):
if not self.is_upper_case_with_underscores(var.name):
info = ["Variable ", var," used l, O, I, which should not be used\n"]
json = self.generate_json_result(info)
self.add_variable_to_json(var, json, {
res = self.generate_result(info)
res.add(var, {
"target": "variable",
"convention": "l_O_I_should_not_be_used"
})
results.append(json)
results.append(res)
if var.is_constant is True:
# For ERC20 compatibility
@ -146,12 +146,12 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
if not self.is_upper_case_with_underscores(var.name):
info = ["Constant ", var," is not in UPPER_CASE_WITH_UNDERSCORES\n"]
json = self.generate_json_result(info)
self.add_variable_to_json(var, json, {
res = self.generate_result(info)
res.add(var, {
"target": "variable_constant",
"convention": "UPPER_CASE_WITH_UNDERSCORES"
})
results.append(json)
results.append(res)
else:
if var.visibility == 'private':
@ -161,34 +161,34 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
if not correct_naming:
info = ["Variable ", var, " is not in mixedCase\n"]
json = self.generate_json_result(info)
self.add_variable_to_json(var, json, {
res = self.generate_result(info)
res.add(var, {
"target": "variable",
"convention": "mixedCase"
})
results.append(json)
results.append(res)
for enum in contract.enums_declared:
if not self.is_cap_words(enum.name):
info = ["Enum ", enum, " is not in CapWords\n"]
json = self.generate_json_result(info)
self.add_enum_to_json(enum, json, {
res = self.generate_result(info)
res.add(enum, {
"target": "enum",
"convention": "CapWords"
})
results.append(json)
results.append(res)
for modifier in contract.modifiers_declared:
if not self.is_mixed_case(modifier.name):
info = ["Modifier ", modifier, " is not in mixedCase\n"]
json = self.generate_json_result(info)
self.add_function_to_json(modifier, json, {
res = self.generate_result(info)
res.add(modifier, {
"target": "modifier",
"convention": "mixedCase"
})
results.append(json)
results.append(res)
return results

@ -75,8 +75,8 @@ class Timestamp(AbstractDetector):
for node in nodes:
info += ['\t- ', node, '\n']
json = self.generate_json_result(info)
res = self.generate_result(info)
results.append(json)
results.append(res)
return results

@ -53,8 +53,8 @@ class LowLevelCalls(AbstractDetector):
for node in nodes:
info += ['\t- ', node, '\n']
json = self.generate_json_result(info)
res = self.generate_result(info)
results.append(json)
results.append(res)
return results

@ -76,9 +76,9 @@ contract MyConc{
for node in unused_return:
info = [f, f" ignores return value by ", node, "\n"]
json = self.generate_json_result(info)
res = self.generate_result(info)
results.append(json)
results.append(res)
return results

@ -39,7 +39,7 @@ By reading B's constructor definition, the reader might assume that `A()` initia
info = ["Void constructor called in ", cst, ":\n"]
info += ["\t- ", node, "\n"]
json = self.generate_json_result(info)
res = self.generate_result(info)
results.append(json)
results.append(res)
return results

@ -95,14 +95,14 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr
# Create our JSON result
json = self.generate_json_result(info)
res = self.generate_result(info)
# Add the function with the re-entrancy first
self.add_function_to_json(func, json)
res.add(func)
# Add all underlying calls in the function which are potentially problematic.
for call_info in calls:
self.add_node_to_json(call_info, json, {
res.add(call_info, {
"underlying_type": "external_calls"
})
@ -111,18 +111,18 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr
# If the calls are not the same ones that send eth, add the eth sending nodes.
if calls != send_eth:
for call_info in send_eth:
self.add_node_to_json(call_info, json, {
res.add(call_info, {
"underlying_type": "external_calls_sending_eth"
})
# Add all variables written via nodes which write them.
for (v, node) in varsWritten:
self.add_node_to_json(node, json, {
res.add(node, {
"underlying_type": "variables_written",
"variable_name": v.name
})
# Append our result
results.append(json)
results.append(res)
return results

@ -95,14 +95,14 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m
info += ['\t- ', v, ' in ', node, '\n']
# Create our JSON result
json = self.generate_json_result(info)
res = self.generate_result(info)
# Add the function with the re-entrancy first
self.add_function_to_json(func, json)
res.add(func)
# Add all underlying calls in the function which are potentially problematic.
for call_info in calls:
self.add_node_to_json(call_info, json, {
res.add(call_info, {
"underlying_type": "external_calls"
})
@ -111,18 +111,18 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m
# If the calls are not the same ones that send eth, add the eth sending nodes.
if calls != send_eth:
for call_info in send_eth:
self.add_node_to_json(call_info, json, {
res.add(call_info, {
"underlying_type": "external_calls_sending_eth"
})
# Add all variables written via nodes which write them.
for (v, node) in varsWritten:
self.add_node_to_json(node, json, {
res.add(node, {
"underlying_type": "variables_written",
"variable_name": v.name
})
# Append our result
results.append(json)
results.append(res)
return results

@ -88,25 +88,25 @@ Do not report reentrancies that involve ethers (see `reentrancy-eth`)'''
info += ['\t- ', v, ' in ', node, '\n']
# Create our JSON result
json = self.generate_json_result(info)
res = self.generate_result(info)
# Add the function with the re-entrancy first
self.add_function_to_json(func, json)
res.add(func)
# Add all underlying calls in the function which are potentially problematic.
for call_info in calls:
self.add_node_to_json(call_info, json, {
res.add(call_info, {
"underlying_type": "external_calls"
})
# Add all variables written via nodes which write them.
for (v, node) in varsWritten:
self.add_node_to_json(node, json, {
res.add(node, {
"underlying_type": "variables_written",
"variable_name": v.name
})
# Append our result
results.append(json)
results.append(res)
return results

@ -69,8 +69,8 @@ contract DerivedContract is BaseContract{
for var in variables:
info += ["\t- ", var, "\n"]
json = self.generate_json_result(info)
res = self.generate_result(info)
results.append(json)
results.append(res)
return results

@ -127,7 +127,7 @@ contract Bug {
info = [shadow_object, f' ({shadow_type}) shadows built-in symbol"\n']
json = self.generate_json_result(info)
results.append(json)
res = self.generate_result(info)
results.append(res)
return results

@ -109,8 +109,8 @@ contract Bug {
info += ["\t- ", overshadowed_entry[1], f" ({overshadowed_entry[0]})\n"]
# Generate relevant JSON data for this shadowing definition.
json = self.generate_json_result(info)
res = self.generate_result(info)
results.append(json)
results.append(res)
return results

@ -80,9 +80,8 @@ contract DerivedContract is BaseContract{
for var in variables:
info += ["\t- ", var, "\n"]
json = self.generate_json_result(info)
results.append(json)
res = self.generate_result(info)
results.append(res)
return results

@ -73,12 +73,11 @@ contract Token
# We have a patch, so pattern.find will return at least one result
info += f"\t- {pattern.findall(source_encoded)[0]}\n"
json = self.generate_json_result(info)
self.add_other_to_json("rtlo-character",
(filename, idx, len(self.RTLO_CHARACTER_ENCODED)),
json,
self.slither)
results.append(json)
res = self.generate_result(info)
res.add_other("rtlo-character",
(filename, idx, len(self.RTLO_CHARACTER_ENCODED)),
self.slither)
results.append(res)
# Advance the start index for the next iteration
start_index = result_index + 1

@ -56,7 +56,7 @@ class Assembly(AbstractDetector):
for node in nodes:
info += ["\t- ", node, "\n"]
json = self.generate_json_result(info)
results.append(json)
res = self.generate_result(info)
results.append(res)
return results

@ -88,7 +88,7 @@ If one of the destinations has a fallback function which reverts, `bad` will alw
func = node.function
info = [func, " has external calls inside a loop: ", node, "\n"]
json = self.generate_json_result(info)
results.append(json)
res = self.generate_result(info)
results.append(res)
return results

@ -51,7 +51,7 @@ Bob calls `delegate` and delegates the execution to its malicious contract. As a
for node in nodes:
node_info = func_info + ['\t- ', node,'\n']
json = self.generate_json_result(node_info)
results.append(json)
res = self.generate_result(node_info)
results.append(res)
return results

@ -157,7 +157,7 @@ contract ContractWithDeprecatedReferences {
for (dep_id, original_desc, recommended_disc) in deprecated_entries:
info += [f"\t- Usage of \"{original_desc}\" should be replaced with \"{recommended_disc}\"\n"]
json = self.generate_json_result(info)
results.append(json)
res = self.generate_result(info)
results.append(res)
return results

@ -120,7 +120,7 @@ contract Crowdsale{
for node in nodes:
node_info = func_info + [f"\t- ", node, "\n"]
json = self.generate_json_result(node_info)
results.append(json)
res = self.generate_result(node_info)
results.append(res)
return results

@ -69,7 +69,7 @@ Use:
node_info = func_info + ['\n\t- ', node,'\n']
# Add the result in result
json = self.generate_json_result(node_info)
results.append(json)
res = self.generate_result(node_info)
results.append(res)
return results

@ -69,8 +69,7 @@ Bob is the owner of `TxOrigin`. Bob calls Eve's contract. Eve's contract calls `
for node in nodes:
info = [func, " uses tx.origin for authorization: ", node, "\n"]
json = self.generate_json_result(info)
results.append(json)
res = self.generate_result(info)
results.append(res)
return results

@ -89,7 +89,7 @@ class ConstCandidateStateVars(AbstractDetector):
# Create a result for each finding
for v in constable_variables:
info = [v, " should be constant\n"]
json = self.generate_json_result(info)
json = self.generate_result(info)
results.append(json)
return results

@ -99,7 +99,7 @@ Bob calls `transfer`. As a result, the ethers are sent to the address 0x0 and ar
for(function, uninitialized_local_variable) in all_results:
info = [uninitialized_local_variable, " is a local variable never initialiazed\n"]
json = self.generate_json_result(info)
json = self.generate_result(info)
results.append(json)
return results

@ -93,7 +93,7 @@ Initialize all the variables. If a variable is meant to be initialized to zero,
for f in functions:
info += ["\t- ", f, "\n"]
json = self.generate_json_result(info)
json = self.generate_result(info)
results.append(json)
return results

@ -102,7 +102,7 @@ Bob calls `func`. As a result, `owner` is override to 0.
for(function, uninitialized_storage_variable) in self.results:
info = [uninitialized_storage_variable, " is a storage variable never initialiazed\n"]
json = self.generate_json_result(info)
json = self.generate_result(info)
results.append(json)
return results

@ -61,7 +61,7 @@ class UnusedStateVars(AbstractDetector):
if unusedVars:
for var in unusedVars:
info = [var, " is never used in ", c, "\n"]
json = self.generate_json_result(info)
json = self.generate_result(info)
results.append(json)
return results

@ -1,6 +1,6 @@
import abc
from slither.utils import json_utils
from slither.utils import output
class IncorrectPrinterInitialization(Exception):
@ -33,38 +33,14 @@ class AbstractPrinter(metaclass=abc.ABCMeta):
self.logger.info(info)
def generate_json_result(self, info, additional_fields=None):
def generate_output(self, info, additional_fields=None):
if additional_fields is None:
additional_fields = {}
d = json_utils.generate_json_result(info, additional_fields)
d['printer'] = self.ARGUMENT
d = output.Output(info, additional_fields)
d.data['printer'] = self.ARGUMENT
return d
@staticmethod
def add_contract_to_json(e, d, additional_fields=None):
json_utils.add_contract_to_json(e, d, additional_fields=additional_fields)
@staticmethod
def add_function_to_json(e, d, additional_fields=None):
json_utils.add_function_to_json(e, d, additional_fields=additional_fields)
@staticmethod
def add_functions_to_json(e, d, additional_fields=None):
json_utils.add_functions_to_json(e, d, additional_fields=additional_fields)
@staticmethod
def add_file_to_json(e, content, d, additional_fields=None):
json_utils.add_file_to_json(e, content, d, additional_fields)
@staticmethod
def add_pretty_table_to_json(e, content, d, additional_fields=None):
json_utils.add_pretty_table_to_json(e, content, d, additional_fields)
@staticmethod
def add_other_to_json(name, source_mapping, d, slither, additional_fields=None):
json_utils.add_other_to_json(name, source_mapping, d, slither, additional_fields)
@abc.abstractmethod
def output(self, filename):
"""TODO Documentation"""

@ -176,9 +176,9 @@ class PrinterCallGraph(AbstractPrinter):
results.append((filename, content))
self.info(info)
json = self.generate_json_result(info)
res = self.generate_output(info)
for filename, content in results:
self.add_file_to_json(filename, content, json)
res.add_file(filename, content)
return json
return res

@ -48,8 +48,8 @@ class PrinterWrittenVariablesAndAuthorization(AbstractPrinter):
txt += str(table) + '\n'
self.info(txt)
json = self.generate_json_result(txt)
res = self.generate_output(txt)
for name, table in all_tables:
self.add_pretty_table_to_json(table, name, json)
res.add_pretty_table(table, name)
return json
return res

@ -31,7 +31,7 @@ class CFG(AbstractPrinter):
self.info(info)
json = self.generate_json_result(info)
res = self.generate_output(info)
for filename, content in all_files:
self.add_file_to_json(filename, content, json)
return json
res.add_file(filename, content)
return res

@ -140,4 +140,6 @@ class Echidna(AbstractPrinter):
self.info(json.dumps(d, indent=4))
return d
res = self.generate_output(json.dumps(d, indent=4))
return res

@ -75,6 +75,6 @@ class PrinterInheritance(AbstractPrinter):
result['base_to_child'][base.name]['not_immediate'] = list(map(str, immediate))
self.info(info)
json = self.generate_json_result(info, additional_fields=result)
res = self.generate_output(info, additional_fields=result)
return json
return res

@ -172,7 +172,7 @@ class PrinterInheritanceGraph(AbstractPrinter):
with open(filename, 'w', encoding='utf8') as f:
f.write(content)
json = self.generate_json_result(info)
self.add_file_to_json(filename, content, json)
res = self.generate_output(info)
res.add_file(filename, content)
return json
return res

@ -2,46 +2,51 @@
Module printing summary of the contract
"""
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils import output
class ConstructorPrinter(AbstractPrinter):
WIKI = 'https://github.com/crytic/slither/wiki/Printer-documentation#constructor-calls'
ARGUMENT = 'constructor-calls'
HELP = 'Print the constructors executed'
WIKI = 'https://github.com/crytic/slither/wiki/Printer-documentation#constructor-calls'
ARGUMENT = 'constructor-calls'
HELP = 'Print the constructors executed'
def _get_soruce_code(self, cst):
src_mapping = cst.source_mapping
content = self.slither.source_code[src_mapping['filename_absolute']]
start = src_mapping['start']
end = src_mapping['start'] + src_mapping['length']
initial_space = src_mapping['starting_column']
return ' ' * initial_space + content[start:end]
def _get_soruce_code(self,cst):
src_mapping = cst.source_mapping
content= self.slither.source_code[src_mapping['filename_absolute']]
start = src_mapping['start']
end = src_mapping['start'] + src_mapping['length']
initial_space = src_mapping['starting_column']
return ' ' * initial_space + content[start:end]
def output(self, _filename):
info = ''
for contract in self.contracts:
stack_name = []
stack_definition = []
info += "\n\nContact Name: " + contract.name
info += " Constructor Call Sequence: "
cst = contract.constructors_declared
if cst:
stack_name.append(contract.name)
stack_definition.append(self._get_soruce_code(cst))
for inherited_contract in contract.inheritance:
cst = inherited_contract.constructors_declared
if cst:
stack_name.append(inherited_contract.name)
stack_definition.append(self._get_soruce_code(cst))
if len(stack_name) > 0:
info += " " + ' '.join(stack_name[len(stack_name) - 1])
count = len(stack_name) - 2
while count >= 0:
info += "-->" + ' '.join(stack_name[count])
count = count - 1
info += "\n Constructor Definitions:"
count = len(stack_definition) - 1
while count >= 0:
info += "\n Contract name:" + str(stack_name[count])
info += "\n" + str(stack_definition[count])
count = count - 1
def output(self,_filename):
for contract in self.contracts:
stack_name = []
stack_definition = []
print("\n\nContact Name:",contract.name)
print(" Constructor Call Sequence: ", sep=' ', end='', flush=True)
cst = contract.constructors_declared
if cst:
stack_name.append(contract.name)
stack_definition.append(self._get_soruce_code(cst))
for inherited_contract in contract.inheritance:
cst = inherited_contract.constructors_declared
if cst:
stack_name.append(inherited_contract.name)
stack_definition.append(self._get_soruce_code(cst))
if len(stack_name)>0:
print(" ",stack_name[len(stack_name)-1], sep=' ', end='', flush=True)
count = len(stack_name)-2
while count>=0:
print("-->",stack_name[count], sep=' ', end='', flush=True)
count= count-1
print("\n Constructor Definitions:")
count = len(stack_definition)-1
while count>=0:
print("\n Contract name:", stack_name[count])
print ("\n", stack_definition[count])
count = count-1
self.info(info)
res = output.Output(info)
return res

@ -3,6 +3,7 @@
"""
import collections
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils import output
from slither.utils.colors import blue, green, magenta
@ -24,7 +25,7 @@ class ContractSummary(AbstractPrinter):
all_contracts = []
for c in self.contracts:
txt += blue("\n+ Contract %s\n" % c.name)
additional_fields = {"elements": []}
additional_fields = output.Output('')
# Order the function with
# contract_declarer -> list_functions
@ -47,15 +48,15 @@ class ContractSummary(AbstractPrinter):
if function.visibility not in ['external', 'public', 'internal', 'private']:
txt += " - {}  ({})\n".format(function, function.visibility)
self.add_function_to_json(function, additional_fields, additional_fields={"visibility":
function.visibility})
additional_fields.add(function, additional_fields={"visibility":
function.visibility})
all_contracts.append((c, additional_fields))
all_contracts.append((c, additional_fields.data))
self.info(txt)
json = self.generate_json_result(txt)
res = self.generate_output(txt)
for contract, additional_fields in all_contracts:
self.add_contract_to_json(contract, json, additional_fields=additional_fields)
res.add(contract, additional_fields=additional_fields)
return json
return res

@ -51,8 +51,8 @@ class DataDependency(AbstractPrinter):
all_txt += txt
all_tables.append((c.name, table))
json = self.generate_json_result(all_txt)
res = self.generate_output(all_txt)
for name, table in all_tables:
self.add_pretty_table_to_json(table, name, json)
res.add_pretty_table(table, name)
return json
return res

@ -70,8 +70,8 @@ class FunctionSummary(AbstractPrinter):
all_tables.append((name, table))
all_txt += txt
json = self.generate_json_result(all_txt)
res = self.generate_output(all_txt)
for name, table in all_tables:
self.add_pretty_table_to_json(table, name, json)
res.add_pretty_table(table, name)
return json
return res

@ -37,8 +37,8 @@ class FunctionIds(AbstractPrinter):
self.info(txt)
json = self.generate_json_result(txt)
res = self.generate_output(txt)
for name, table in all_tables:
self.add_pretty_table_to_json(table, name, json)
res.add_pretty_table(table, name)
return json
return res

@ -4,6 +4,7 @@ Module printing summary of the contract
import logging
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils import output
from slither.utils.code_complexity import compute_cyclomatic_complexity
from slither.utils.colors import green, red, yellow
from slither.utils.standard_libraries import is_standard_library
@ -205,6 +206,7 @@ class PrinterHumanSummary(AbstractPrinter):
'ercs': [],
}
lines_number = self._lines_number()
if lines_number:
total_lines, total_dep_lines = lines_number
@ -241,6 +243,7 @@ class PrinterHumanSummary(AbstractPrinter):
self.info(txt)
results_contract = output.Output('')
for contract in self.slither.contracts_derived:
optimization, info, low, medium, high = self._get_detectors_result()
contract_d = {'contract_name': contract.name,
@ -262,9 +265,11 @@ class PrinterHumanSummary(AbstractPrinter):
contract_d['erc20_can_mint'] = False
contract_d['erc20_race_condition_mitigated'] = race_condition_mitigated
self.add_contract_to_json(contract, results['contracts'], additional_fields=contract_d)
results_contract.add_contract(contract, additional_fields=contract_d)
results['contracts']['elements'] = results_contract.elements
json = self.generate_json_result(txt, additional_fields=results)
json = self.generate_output(txt, additional_fields=results)
return json

@ -40,8 +40,8 @@ class Modifiers(AbstractPrinter):
txt += "\n"+str(table)
self.info(txt)
json = self.generate_json_result(all_txt)
res = self.generate_output(all_txt)
for name, table in all_tables:
self.add_pretty_table_to_json(table, name, json)
res.add_pretty_table(table, name)
return json
return res

@ -45,8 +45,8 @@ class RequireOrAssert(AbstractPrinter):
all_tables.append((contract.name, table))
all_txt += txt
json = self.generate_json_result(all_txt)
res = self.generate_output(all_txt)
for name, table in all_tables:
self.add_pretty_table_to_json(table, name, json)
res.add_pretty_table(table, name)
return json
return res

@ -4,8 +4,8 @@
from slither.printers.abstract_printer import AbstractPrinter
class PrinterSlithIR(AbstractPrinter):
class PrinterSlithIR(AbstractPrinter):
ARGUMENT = 'slithir'
HELP = 'Print the slithIR representation of the functions'
@ -20,30 +20,32 @@ class PrinterSlithIR(AbstractPrinter):
txt = ""
for contract in self.contracts:
print('Contract {}'.format(contract.name))
txt += 'Contract {}'.format(contract.name)
for function in contract.functions:
print(f'\tFunction {function.canonical_name} {"" if function.is_shadowed else "(*)"}')
txt += f'\tFunction {function.canonical_name} {"" if function.is_shadowed else "(*)"}'
for node in function.nodes:
if node.expression:
print('\t\tExpression: {}'.format(node.expression))
print('\t\tIRs:')
txt += '\t\tExpression: {}'.format(node.expression)
txt += '\t\tIRs:'
for ir in node.irs:
print('\t\t\t{}'.format(ir))
txt += '\t\t\t{}'.format(ir)
elif node.irs:
print('\t\tIRs:')
txt += '\t\tIRs:'
for ir in node.irs:
print('\t\t\t{}'.format(ir))
txt += '\t\t\t{}'.format(ir)
for modifier_statement in function.modifiers_statements:
print(f'\t\tModifier Call {modifier_statement.entry_point.expression}')
txt += f'\t\tModifier Call {modifier_statement.entry_point.expression}'
for modifier_statement in function.explicit_base_constructor_calls_statements:
print(f'\t\tConstructor Call {modifier_statement.entry_point.expression}')
txt += f'\t\tConstructor Call {modifier_statement.entry_point.expression}'
for modifier in contract.modifiers:
print('\tModifier {}'.format(modifier.canonical_name))
txt += '\tModifier {}'.format(modifier.canonical_name)
for node in modifier.nodes:
print(node)
txt += str(node)
if node.expression:
print('\t\tExpression: {}'.format(node.expression))
print('\t\tIRs:')
txt += '\t\tExpression: {}'.format(node.expression)
txt += '\t\tIRs:'
for ir in node.irs:
print('\t\t\t{}'.format(ir))
txt += '\t\t\t{}'.format(ir)
self.info(txt)
res = self.generate_output(txt)
return res

@ -21,24 +21,26 @@ class PrinterSlithIRSSA(AbstractPrinter):
txt = ""
for contract in self.contracts:
print('Contract {}'.format(contract.name))
txt += 'Contract {}'.format(contract.name)
for function in contract.functions:
print('\tFunction {}'.format(function.canonical_name))
txt += '\tFunction {}'.format(function.canonical_name)
for node in function.nodes:
if node.expression:
print('\t\tExpression: {}'.format(node.expression))
txt += '\t\tExpression: {}'.format(node.expression)
if node.irs_ssa:
print('\t\tIRs:')
txt += '\t\tIRs:'
for ir in node.irs_ssa:
print('\t\t\t{}'.format(ir))
txt += '\t\t\t{}'.format(ir)
for modifier in contract.modifiers:
print('\tModifier {}'.format(modifier.canonical_name))
txt += '\tModifier {}'.format(modifier.canonical_name)
for node in modifier.nodes:
print(node)
txt += str(node)
if node.expression:
print('\t\tExpression: {}'.format(node.expression))
txt += '\t\tExpression: {}'.format(node.expression)
if node.irs_ssa:
print('\t\tIRs:')
txt += '\t\tIRs:'
for ir in node.irs_ssa:
print('\t\t\t{}'.format(ir))
txt += '\t\t\t{}'.format(ir)
self.info(txt)
res = self.generate_output(txt)
return res

@ -36,8 +36,7 @@ class VariableOrder(AbstractPrinter):
self.info(txt)
json = self.generate_json_result(txt)
res = self.generate_output(txt)
for name, table in all_tables:
self.add_pretty_table_to_json(table, name, json)
return json
res.add_pretty_table(table, name)
return res

@ -165,7 +165,7 @@ class Slither(SlitherSolc):
:return: List of registered printers outputs.
"""
return [p.output(self.filename) for p in self._printers]
return [p.output(self.filename).data for p in self._printers]
def _check_common_things(self, thing_name, cls, base_cls, instances_list):

@ -5,7 +5,7 @@ from collections import defaultdict
from slither import Slither
from crytic_compile import cryticparser
from slither.utils.erc import ERCS
from slither.utils.json_utils import output_json
from slither.utils.output import output_to_json
from .erc.ercs import generic_erc_checks
from .erc.erc20 import check_erc20
@ -59,7 +59,7 @@ def parse_args():
def _log_error(err, args):
if args.json:
output_json(args.json, str(err), {"upgradeability-check": []})
output_to_json(args.json, str(err), {"upgradeability-check": []})
logger.error(err)
@ -92,7 +92,7 @@ def main():
return
if args.json:
output_json(args.json, None, {"upgradeability-check": ret})
output_to_json(args.json, None, {"upgradeability-check": ret})
if __name__ == '__main__':

@ -1,6 +1,6 @@
import logging
from slither.utils import json_utils
from slither.utils import output
logger = logging.getLogger("Slither-conformance")
@ -18,9 +18,9 @@ def approval_race_condition(contract, ret):
txt = f'\t[ ] {contract.name} is not protected for the ERC20 approval race condition'
logger.info(txt)
lack_of_erc20_race_condition_protection = json_utils.generate_json_result(txt)
json_utils.add_contract_to_json(contract, lack_of_erc20_race_condition_protection)
ret["lack_of_erc20_race_condition_protection"].append(lack_of_erc20_race_condition_protection)
lack_of_erc20_race_condition_protection = output.Output(txt)
lack_of_erc20_race_condition_protection.add(contract)
ret["lack_of_erc20_race_condition_protection"].append(lack_of_erc20_race_condition_protection.data)
def check_erc20(contract, ret, explored=None):

@ -1,7 +1,7 @@
import logging
from slither.slithir.operations import EventCall
from slither.utils import json_utils
from slither.utils import output
from slither.utils.type import export_nested_types_from_variable, export_return_type_from_variable
logger = logging.getLogger("Slither-conformance")
@ -25,12 +25,12 @@ def _check_signature(erc_function, contract, ret):
if not state_variable_as_function or not state_variable_as_function.visibility in ['public', 'external']:
txt = f'[ ] {sig} is missing {"" if required else "(optional)"}'
logger.info(txt)
missing_func = json_utils.generate_json_result(txt, additional_fields={
missing_func = output.Output(txt, additional_fields={
"function": sig,
"required": required
})
json_utils.add_contract_to_json(contract, missing_func)
ret["missing_function"].append(missing_func)
missing_func.add(contract)
ret["missing_function"].append(missing_func.data)
return
types = [str(x) for x in export_nested_types_from_variable(state_variable_as_function)]
@ -38,12 +38,12 @@ def _check_signature(erc_function, contract, ret):
if types != parameters:
txt = f'[ ] {sig} is missing {"" if required else "(optional)"}'
logger.info(txt)
missing_func = json_utils.generate_json_result(txt, additional_fields={
missing_func = output.Output(txt, additional_fields={
"function": sig,
"required": required
})
json_utils.add_contract_to_json(contract, missing_func)
ret["missing_function"].append(missing_func)
missing_func.add(contract)
ret["missing_function"].append(missing_func.data)
return
function_return_type = [export_return_type_from_variable(state_variable_as_function)]
@ -65,12 +65,12 @@ def _check_signature(erc_function, contract, ret):
txt = f'\t[ ] {sig} -> () should return {return_type}'
logger.info(txt)
incorrect_return = json_utils.generate_json_result(txt, additional_fields={
incorrect_return = output.Output(txt, additional_fields={
"expected_return_type": return_type,
"actual_return_type": function_return_type
})
json_utils.add_function_to_json(function, incorrect_return)
ret["incorrect_return_type"].append(incorrect_return)
incorrect_return.add(function)
ret["incorrect_return_type"].append(incorrect_return.data)
elif not return_type:
txt = f'\t[✓] {sig} -> () (correct return type)'
@ -79,12 +79,12 @@ def _check_signature(erc_function, contract, ret):
txt = f'\t[ ] {sig} -> () should return {return_type}'
logger.info(txt)
incorrect_return = json_utils.generate_json_result(txt, additional_fields={
incorrect_return = output.Output(txt, additional_fields={
"expected_return_type": return_type,
"actual_return_type": function_return_type
})
json_utils.add_function_to_json(function, incorrect_return)
ret["incorrect_return_type"].append(incorrect_return)
incorrect_return.add(function)
ret["incorrect_return_type"].append(incorrect_return.data)
if view:
if function_view:
@ -94,9 +94,9 @@ def _check_signature(erc_function, contract, ret):
txt = f'\t[ ] {sig} should be view'
logger.info(txt)
should_be_view = json_utils.generate_json_result(txt)
json_utils.add_function_to_json(function, should_be_view)
ret["should_be_view"].append(should_be_view)
should_be_view = output.Output(txt)
should_be_view.add(function)
ret["should_be_view"].append(should_be_view.data)
if events:
for event in events:
@ -106,11 +106,11 @@ def _check_signature(erc_function, contract, ret):
txt = f'\t[ ] Must emit be view {event_sig}'
logger.info(txt)
missing_event_emmited = json_utils.generate_json_result(txt, additional_fields={
missing_event_emmited = output.Output(txt, additional_fields={
"missing_event": event_sig
})
json_utils.add_function_to_json(function, missing_event_emmited)
ret["missing_event_emmited"].append(missing_event_emmited)
missing_event_emmited.add(function)
ret["missing_event_emmited"].append(missing_event_emmited.data)
else:
event_found = False
@ -127,11 +127,11 @@ def _check_signature(erc_function, contract, ret):
txt = f'\t[ ] Must emit be view {event_sig}'
logger.info(txt)
missing_event_emmited = json_utils.generate_json_result(txt, additional_fields={
missing_event_emmited = output.Output(txt, additional_fields={
"missing_event": event_sig
})
json_utils.add_function_to_json(function, missing_event_emmited)
ret["missing_event_emmited"].append(missing_event_emmited)
missing_event_emmited.add(function)
ret["missing_event_emmited"].append(missing_event_emmited.data)
def _check_events(erc_event, contract, ret):
@ -146,12 +146,11 @@ def _check_events(erc_event, contract, ret):
txt = f'[ ] {sig} is missing'
logger.info(txt)
missing_event = json_utils.generate_json_result(txt, additional_fields={
missing_event = output.Output(txt, additional_fields={
"event": sig
})
json_utils.add_contract_to_json(contract, missing_event)
ret["missing_event"].append(missing_event)
missing_event.add(contract)
ret["missing_event"].append(missing_event.data)
return
txt = f'[✓] {sig} is present'
@ -166,11 +165,11 @@ def _check_events(erc_event, contract, ret):
txt = f'\t[ ] parameter {i} should be indexed'
logger.info(txt)
missing_event_index = json_utils.generate_json_result(txt, additional_fields={
missing_event_index = output.Output(txt, additional_fields={
"missing_index": i
})
json_utils.add_event_to_json(event, missing_event_index)
ret["missing_event_index"].append(missing_event_index)
missing_event_index.add_event(event)
ret["missing_event_index"].append(missing_event_index.data)
def generic_erc_checks(contract, erc_functions, erc_events, ret, explored=None):

@ -4,17 +4,19 @@ import logging
from slither.core.declarations import Function
from slither.core.variables.variable import Variable
from slither.utils.colors import yellow, green, red
from slither.utils import json_utils
from slither.utils import output
logging.basicConfig(level=logging.WARNING)
logger = logging.getLogger('Slither.kspec')
def _refactor_type(type):
return {
'uint': 'uint256',
'int': 'int256'
}.get(type, type)
def _get_all_covered_kspec_functions(target):
# Create a set of our discovered functions which are covered
covered_functions = set()
@ -59,6 +61,7 @@ def _get_slither_functions(slither):
return slither_functions
def _generate_output(kspec, message, color, generate_json):
info = ""
for function in kspec:
@ -67,12 +70,13 @@ def _generate_output(kspec, message, color, generate_json):
logger.info(color(info))
if generate_json:
json_kspec_present = json_utils.generate_json_result(info)
json_kspec_present = output.Output(info)
for function in kspec:
json_utils.add_function_to_json(function, json_kspec_present)
return json_kspec_present
json_kspec_present.add(function)
return json_kspec_present.data
return None
def _generate_output_unresolved(kspec, message, color, generate_json):
info = ""
for contract, function in kspec:
@ -81,8 +85,8 @@ def _generate_output_unresolved(kspec, message, color, generate_json):
logger.info(color(info))
if generate_json:
json_kspec_present = json_utils.generate_json_result(info, additional_fields={"signatures": kspec})
return json_kspec_present
json_kspec_present = output.Output(info, additional_fields={"signatures": kspec})
return json_kspec_present.data
return None
@ -95,7 +99,6 @@ def _run_coverage_analysis(args, slither, kspec_functions):
kspec_functions_resolved = kspec_functions & slither_functions_set
kspec_functions_unresolved = kspec_functions - kspec_functions_resolved
kspec_missing = []
kspec_present = []
@ -114,9 +117,9 @@ def _run_coverage_analysis(args, slither, kspec_functions):
red,
args.json)
json_kspec_missing_variables = _generate_output([f for f in kspec_missing if isinstance(f, Variable)],
"[ ] (Missing variable)",
yellow,
args.json)
"[ ] (Missing variable)",
yellow,
args.json)
json_kspec_unresolved = _generate_output_unresolved(kspec_functions_unresolved,
"[ ] (Unresolved)",
yellow,
@ -124,12 +127,13 @@ def _run_coverage_analysis(args, slither, kspec_functions):
# Handle unresolved kspecs
if args.json:
json_utils.output_json(args.json, None, {
"functions_present": json_kspec_present,
"functions_missing": json_kspec_missing_functions,
"variables_missing": json_kspec_missing_variables,
"functions_unresolved": json_kspec_unresolved
})
output.output_to_json(args.json, None, {
"functions_present": json_kspec_present,
"functions_missing": json_kspec_missing_functions,
"variables_missing": json_kspec_missing_variables,
"functions_unresolved": json_kspec_unresolved
})
def run_analysis(args, slither, kspec):
# Get all of our kspec'd functions (tuple(contract_name, function_name)).
@ -143,4 +147,3 @@ def run_analysis(args, slither, kspec):
# Run coverage analysis
_run_coverage_analysis(args, slither, kspec_functions)

@ -7,7 +7,7 @@ from slither import Slither
from crytic_compile import cryticparser
from slither.exceptions import SlitherException
from slither.utils.colors import red
from slither.utils.json_utils import output_json
from slither.utils.output import output_to_json
from .compare_variables_order import compare_variables_order
from .compare_function_ids import compare_function_ids
@ -135,7 +135,7 @@ def main():
info = 'Contract {} not found in {}'.format(v1_name, v1.filename)
logger.error(red(info))
if args.json:
output_json(args.json, str(info), {"upgradeability-check": json_results})
output_to_json(args.json, str(info), {"upgradeability-check": json_results})
return
_checks_on_contract(v1_contract, json_results)
@ -153,7 +153,7 @@ def main():
info = 'Proxy {} not found in {}'.format(args.proxy_name, proxy.filename)
logger.error(red(info))
if args.json:
output_json(args.json, str(info), {"upgradeability-check": json_results})
output_to_json(args.json, str(info), {"upgradeability-check": json_results})
return
json_results['proxy-present'] = True
_checks_on_contract_and_proxy(v1_contract, proxy_contract, json_results)
@ -170,7 +170,7 @@ def main():
info = 'New logic contract {} not found in {}'.format(args.new_contract_name, v2.filename)
logger.error(red(info))
if args.json:
output_json(args.json, str(info), {"upgradeability-check": json_results})
output_to_json(args.json, str(info), {"upgradeability-check": json_results})
return
json_results['contract_v2-present'] = True
@ -183,12 +183,12 @@ def main():
_checks_on_contract_update(v1_contract, v2_contract, json_results)
if args.json:
output_json(args.json, None, {"upgradeability-check": json_results})
output_to_json(args.json, None, {"upgradeability-check": json_results})
except SlitherException as e:
logger.error(str(e))
if args.json:
output_json(args.json, str(e), {"upgradeability-check": json_results})
output_to_json(args.json, str(e), {"upgradeability-check": json_results})
return
# endregion

@ -1,7 +1,7 @@
import logging
from slither.slithir.operations import InternalCall
from slither.utils import json_utils
from slither.utils.output import Output
from slither.utils.colors import red, yellow, green
logger = logging.getLogger("Slither-check-upgradeability")
@ -74,9 +74,9 @@ def check_initialization(contract):
initializer_modifier_missing = True
info = f'{f.canonical_name} does not call the initializer modifier'
logger.info(red(info))
json_elem = json_utils.generate_json_result(info)
json_utils.add_function_to_json(f, json_elem)
results['missing-initializer-modifier'].append(json_elem)
res = Output(info)
res.add(f)
results['missing-initializer-modifier'].append(res.data)
if not initializer_modifier_missing:
logger.info(green('All the init functions have the initializer modifier'))
@ -103,10 +103,10 @@ def check_initialization(contract):
for f in missing_calls:
info = f'Missing call to {f.canonical_name} in {most_derived_init.canonical_name}'
logger.info(red(info))
json_elem = json_utils.generate_json_result(info)
json_utils.add_function_to_json(f, json_elem, {"is_most_derived_init_function": False})
json_utils.add_function_to_json(most_derived_init, json_elem, {"is_most_derived_init_function": True})
results['missing-calls'].append(json_elem)
res = Output(info)
res.add(f, {"is_most_derived_init_function": False})
res.add(most_derived_init, {"is_most_derived_init_function": True})
results['missing-calls'].append(res.data)
missing_call = True
if not missing_call:
logger.info(green('No missing call to an init function found'))
@ -117,9 +117,9 @@ def check_initialization(contract):
for f in double_calls:
info = f'{f.canonical_name} is called multiple times in {most_derived_init.full_name}'
logger.info(red(info))
json_elem = json_utils.generate_json_result(info)
json_utils.add_function_to_json(f, json_elem)
results['multiple-calls'].append(json_elem)
res = Output(info)
res.add(f)
results['multiple-calls'].append(res.data)
double_calls_found = True
if not double_calls_found:
logger.info(green('No double call to init functions found'))
@ -128,9 +128,9 @@ def check_initialization(contract):
init_info = f'{contract.name} needs to be initialized by {most_derived_init.full_name}\n'
logger.info(green('Check the deployement script to ensure that these functions are called:\n' + init_info))
json_elem = json_utils.generate_json_result(init_info)
json_utils.add_function_to_json(most_derived_init, json_elem)
results['initialize_target'] = json_elem
res = Output(init_info)
res.add(most_derived_init)
results['initialize_target'] = res.data
if not error_found:
logger.info(green('No error found'))

@ -1,6 +1,6 @@
import logging
from slither.utils import json_utils
from slither.utils import output
from slither.utils.colors import red, green
logger = logging.getLogger("Slither-check-upgradeability")
@ -20,9 +20,9 @@ def check_variable_initialization(contract):
if s.initialized and not s.is_constant:
info = f'{s.canonical_name} has an initial value ({s.source_mapping_str})'
logger.info(red(info))
json_elem = json_utils.generate_json_result(info)
json_utils.add_variable_to_json(s, json_elem)
results['variables-initialized'].append(json_elem)
res = output.Output(info)
res.add(s)
results['variables-initialized'].append(res.data)
error_found = True
if not error_found:

@ -4,10 +4,9 @@
'''
import logging
from slither import Slither
from slither.core.declarations import Function
from slither.exceptions import SlitherError
from slither.utils import json_utils
from slither.utils.output import Output
from slither.utils.function import get_function_id
from slither.utils.colors import red, green
@ -63,16 +62,10 @@ def compare_function_ids(implem, proxy):
info = f'Function id collision found: {implem_function.canonical_name} ({implem_function.source_mapping_str}) {proxy_function.canonical_name} ({proxy_function.source_mapping_str})'
logger.info(red(info))
json_elem = json_utils.generate_json_result(info)
if isinstance(implem_function, Function):
json_utils.add_function_to_json(implem_function, json_elem)
else:
json_utils.add_variable_to_json(implem_function, json_elem)
if isinstance(proxy_function, Function):
json_utils.add_function_to_json(proxy_function, json_elem)
else:
json_utils.add_variable_to_json(proxy_function, json_elem)
results['function-id-collision'].append(json_elem)
res = Output(info)
res.add(implem_function)
res.add(proxy_function)
results['function-id-collision'].append(res.data)
else:
@ -82,17 +75,10 @@ def compare_function_ids(implem, proxy):
info = f'Shadowing between {implem_function.canonical_name} ({implem_function.source_mapping_str}) and {proxy_function.canonical_name} ({proxy_function.source_mapping_str})'
logger.info(red(info))
json_elem = json_utils.generate_json_result(info)
json_elem = json_utils.generate_json_result(info)
if isinstance(implem_function, Function):
json_utils.add_function_to_json(implem_function, json_elem)
else:
json_utils.add_variable_to_json(implem_function, json_elem)
if isinstance(proxy_function, Function):
json_utils.add_function_to_json(proxy_function, json_elem)
else:
json_utils.add_variable_to_json(proxy_function, json_elem)
results['shadowing'].append(json_elem)
res = Output(info)
res.add(implem_function)
res.add(proxy_function)
results['shadowing'].append(res.data)
if not error_found:
logger.info(green('No error found'))

@ -3,7 +3,7 @@
'''
import logging
from slither.utils import json_utils
from slither.utils.output import Output
from slither.utils.colors import red, green, yellow
logger = logging.getLogger("Slither-check-upgradeability")
@ -32,9 +32,9 @@ def compare_variables_order(contract1, contract2, missing_variable_check=True):
info = f'Variable only in {contract1.name}: {variable1.name} ({variable1.source_mapping_str})'
logger.info(yellow(info))
json_elem = json_utils.generate_json_result(info)
json_utils.add_variable_to_json(variable1, json_elem)
results['missing_variables'].append(json_elem)
res = Output(info)
res.add(variable1)
results['missing_variables'].append(res.data)
error_found = True
continue
@ -47,10 +47,10 @@ def compare_variables_order(contract1, contract2, missing_variable_check=True):
info += f'\t Variable {idx} in {contract2.name}: {variable2.name} {variable2.type} ({variable2.source_mapping_str})\n'
logger.info(red(info))
json_elem = json_utils.generate_json_result(info, additional_fields={'index': idx})
json_utils.add_variable_to_json(variable1, json_elem)
json_utils.add_variable_to_json(variable2, json_elem)
results['different-variables'].append(json_elem)
res = Output(info, additional_fields={'index': idx})
res.add(variable1)
res.add(variable2)
results['different-variables'].append(res.data)
error_found = True
@ -61,9 +61,9 @@ def compare_variables_order(contract1, contract2, missing_variable_check=True):
info = f'Extra variables in {contract2.name}: {variable2.name} ({variable2.source_mapping_str})\n'
logger.info(yellow(info))
json_elem = json_utils.generate_json_result(info, additional_fields={'index': idx})
json_utils.add_variable_to_json(variable2, json_elem)
results['extra-variables'].append(json_elem)
res = Output(info, additional_fields={'index': idx})
res.add(variable2)
results['extra-variables'].append(res.data)
idx = idx + 1
if not error_found:

@ -1,6 +1,6 @@
import logging
from slither.utils import json_utils
from slither.utils.output import Output
from slither.utils.colors import red, yellow, green
logger = logging.getLogger("Slither-check-upgradeability")
@ -49,30 +49,30 @@ def constant_conformance_check(contract_v1, contract_v2):
info = f'{state_v1.canonical_name} ({state_v1.source_mapping_str}) was constant and {state_v2.canonical_name} is not ({state_v2.source_mapping_str})'
logger.info(red(info))
json_elem = json_utils.generate_json_result(info)
json_utils.add_variable_to_json(state_v1, json_elem)
json_utils.add_variable_to_json(state_v2, json_elem)
results['were_constants'].append(json_elem)
res = Output(info)
res.add(state_v1)
res.add(state_v2)
results['were_constants'].append(res.data)
error_found = True
elif state_v2.is_constant:
info = f'{state_v1.canonical_name} ({state_v1.source_mapping_str}) was not constant but {state_v2.canonical_name} is ({state_v2.source_mapping_str})'
logger.info(red(info))
json_elem = json_utils.generate_json_result(info)
json_utils.add_variable_to_json(state_v1, json_elem)
json_utils.add_variable_to_json(state_v2, json_elem)
results['became_constants'].append(json_elem)
res = Output(info)
res.add(state_v1)
res.add(state_v2)
results['became_constants'].append(res.data)
error_found = True
else:
info = f'{state_v1.canonical_name} not found in {contract_v2.name}, not check was done'
logger.info(yellow(info))
json_elem = json_utils.generate_json_result(info)
json_utils.add_variable_to_json(state_v1, json_elem)
json_utils.add_contract_to_json(contract_v2, json_elem)
results['not_found_in_v2'].append(json_elem)
res = Output(info)
res.add(state_v1)
res.add(contract_v2)
results['not_found_in_v2'].append(res.data)
error_found = True

@ -1,452 +0,0 @@
import os
import json
import logging
from collections import OrderedDict
from slither.core.cfg.node import Node
from slither.core.declarations import Contract, Function, Enum, Event, Structure, Pragma
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.variable import Variable
from slither.exceptions import SlitherError
from slither.utils.colors import yellow
logger = logging.getLogger("Slither")
###################################################################################
###################################################################################
# region Output
###################################################################################
###################################################################################
def output_json(filename, error, results):
"""
:param filename: Filename where the json will be written. If None or "-", write to stdout
:param error: Error to report
:param results: Results to report
:param logger: Logger where to log potential info
:return:
"""
# Create our encapsulated JSON result.
json_result = {
"success": error is None,
"error": error,
"results": results
}
if filename == "-":
filename = None
# Determine if we should output to stdout
if filename is None:
# Write json to console
print(json.dumps(json_result))
else:
# Write json to file
if os.path.isfile(filename):
logger.info(yellow(f'{filename} exists already, the overwrite is prevented'))
else:
with open(filename, 'w', encoding='utf8') as f:
json.dump(json_result, f, indent=2)
# endregion
###################################################################################
###################################################################################
# region Json generation
###################################################################################
###################################################################################
def _convert_to_description(d):
if isinstance(d, str):
return d
if not isinstance(d, SourceMapping):
raise SlitherError(f'{d} does not inherit from SourceMapping, conversion impossible')
if isinstance(d, Node):
if d.expression:
return f'{d.expression} ({d.source_mapping_str})'
else:
return f'{str(d)} ({d.source_mapping_str})'
if hasattr(d, 'canonical_name'):
return f'{d.canonical_name} ({d.source_mapping_str})'
if hasattr(d, 'name'):
return f'{d.name} ({d.source_mapping_str})'
raise SlitherError(f'{type(d)} cannot be converted (no name, or canonical_name')
def _convert_to_markdown(d, markdown_root):
if isinstance(d, str):
return d
if not isinstance(d, SourceMapping):
raise SlitherError(f'{d} does not inherit from SourceMapping, conversion impossible')
if isinstance(d, Node):
if d.expression:
return f'[{d.expression}]({d.source_mapping_to_markdown(markdown_root)})'
else:
return f'[{str(d)}]({d.source_mapping_to_markdown(markdown_root)})'
if hasattr(d, 'canonical_name'):
return f'[{d.canonical_name}]({d.source_mapping_to_markdown(markdown_root)})'
if hasattr(d, 'name'):
return f'[{d.name}]({d.source_mapping_to_markdown(markdown_root)})'
raise SlitherError(f'{type(d)} cannot be converted (no name, or canonical_name')
def generate_json_result(info, additional_fields=None, markdown_root='', standard_format=False):
if additional_fields is None:
additional_fields = {}
d = OrderedDict()
d['elements'] = []
d['description'] = ''.join(_convert_to_description(d) for d in info)
d['markdown'] = ''.join(_convert_to_markdown(d, markdown_root) for d in info)
if standard_format:
to_add = [i for i in info if not isinstance(i, str)]
for add in to_add:
if isinstance(add, Variable):
add_variable_to_json(add, d)
elif isinstance(add, Contract):
add_contract_to_json(add, d)
elif isinstance(add, Function):
add_function_to_json(add, d)
elif isinstance(add, Enum):
add_enum_to_json(add, d)
elif isinstance(add, Event):
add_event_to_json(add, d)
elif isinstance(add, Structure):
add_struct_to_json(add, d)
elif isinstance(add, Pragma):
add_pragma_to_json(add, d)
elif isinstance(add, Node):
add_node_to_json(add, d)
else:
raise SlitherError(f'Impossible to add {type(add)} to the json')
if additional_fields:
d['additional_fields'] = additional_fields
return d
# endregion
###################################################################################
###################################################################################
# region Internal functions
###################################################################################
###################################################################################
def _create_base_element(type, name, source_mapping, type_specific_fields=None, additional_fields=None):
if additional_fields is None:
additional_fields = {}
if type_specific_fields is None:
type_specific_fields = {}
element = {'type': type,
'name': name,
'source_mapping': source_mapping}
if type_specific_fields:
element['type_specific_fields'] = type_specific_fields
if additional_fields:
element['additional_fields'] = additional_fields
return element
def _create_parent_element(element):
from slither.core.children.child_contract import ChildContract
from slither.core.children.child_function import ChildFunction
from slither.core.children.child_inheritance import ChildInheritance
if isinstance(element, ChildInheritance):
if element.contract_declarer:
contract = {'elements': []}
add_contract_to_json(element.contract_declarer, contract)
return contract['elements'][0]
elif isinstance(element, ChildContract):
if element.contract:
contract = {'elements': []}
add_contract_to_json(element.contract, contract)
return contract['elements'][0]
elif isinstance(element, ChildFunction):
if element.function:
function = {'elements': []}
add_function_to_json(element.function, function)
return function['elements'][0]
return None
# endregion
###################################################################################
###################################################################################
# region Variables
###################################################################################
###################################################################################
def add_variable_to_json(variable, d, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'parent': _create_parent_element(variable)
}
element = _create_base_element('variable',
variable.name,
variable.source_mapping,
type_specific_fields,
additional_fields)
d['elements'].append(element)
def add_variables_to_json(variables, d):
for variable in sorted(variables, key=lambda x: x.name):
add_variable_to_json(variable, d)
# endregion
###################################################################################
###################################################################################
# region Contract
###################################################################################
###################################################################################
def add_contract_to_json(contract, d, additional_fields=None):
if additional_fields is None:
additional_fields = {}
element = _create_base_element('contract',
contract.name,
contract.source_mapping,
{},
additional_fields)
d['elements'].append(element)
# endregion
###################################################################################
###################################################################################
# region Functions
###################################################################################
###################################################################################
def add_function_to_json(function, d, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'parent': _create_parent_element(function),
'signature': function.full_name
}
element = _create_base_element('function',
function.name,
function.source_mapping,
type_specific_fields,
additional_fields)
d['elements'].append(element)
def add_functions_to_json(functions, d, additional_fields=None):
if additional_fields is None:
additional_fields = {}
for function in sorted(functions, key=lambda x: x.name):
add_function_to_json(function, d, additional_fields)
# endregion
###################################################################################
###################################################################################
# region Enum
###################################################################################
###################################################################################
def add_enum_to_json(enum, d, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'parent': _create_parent_element(enum)
}
element = _create_base_element('enum',
enum.name,
enum.source_mapping,
type_specific_fields,
additional_fields)
d['elements'].append(element)
# endregion
###################################################################################
###################################################################################
# region Structures
###################################################################################
###################################################################################
def add_struct_to_json(struct, d, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'parent': _create_parent_element(struct)
}
element = _create_base_element('struct',
struct.name,
struct.source_mapping,
type_specific_fields,
additional_fields)
d['elements'].append(element)
# endregion
###################################################################################
###################################################################################
# region Events
###################################################################################
###################################################################################
def add_event_to_json(event, d, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'parent': _create_parent_element(event),
'signature': event.full_name
}
element = _create_base_element('event',
event.name,
event.source_mapping,
type_specific_fields,
additional_fields)
d['elements'].append(element)
# endregion
###################################################################################
###################################################################################
# region Nodes
###################################################################################
###################################################################################
def add_node_to_json(node, d, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'parent': _create_parent_element(node),
}
node_name = str(node.expression) if node.expression else ""
element = _create_base_element('node',
node_name,
node.source_mapping,
type_specific_fields,
additional_fields)
d['elements'].append(element)
def add_nodes_to_json(nodes, d):
for node in sorted(nodes, key=lambda x: x.node_id):
add_node_to_json(node, d)
# endregion
###################################################################################
###################################################################################
# region Pragma
###################################################################################
###################################################################################
def add_pragma_to_json(pragma, d, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'directive': pragma.directive
}
element = _create_base_element('pragma',
pragma.version,
pragma.source_mapping,
type_specific_fields,
additional_fields)
d['elements'].append(element)
# endregion
###################################################################################
###################################################################################
# region File
###################################################################################
###################################################################################
def add_file_to_json(filename, content, d, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'filename': filename,
'content': content
}
element = _create_base_element('file',
type_specific_fields,
additional_fields)
d['elements'].append(element)
# endregion
###################################################################################
###################################################################################
# region Pretty Table
###################################################################################
###################################################################################
def add_pretty_table_to_json(content, name, d, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'content': content,
'name': name
}
element = _create_base_element('pretty_table',
type_specific_fields,
additional_fields)
d['elements'].append(element)
# endregion
###################################################################################
###################################################################################
# region Others
###################################################################################
###################################################################################
def add_other_to_json(name, source_mapping, d, slither, additional_fields=None):
# If this a tuple with (filename, start, end), convert it to a source mapping.
if additional_fields is None:
additional_fields = {}
if isinstance(source_mapping, tuple):
# Parse the source id
(filename, start, end) = source_mapping
source_id = next(
(source_unit_id for (source_unit_id, source_unit_filename) in slither.source_units.items() if
source_unit_filename == filename), -1)
# Convert to a source mapping string
source_mapping = f"{start}:{end}:{source_id}"
# If this is a source mapping string, parse it.
if isinstance(source_mapping, str):
source_mapping_str = source_mapping
source_mapping = SourceMapping()
source_mapping.set_offset(source_mapping_str, slither)
# If this is a source mapping object, get the underlying source mapping dictionary
if isinstance(source_mapping, SourceMapping):
source_mapping = source_mapping.source_mapping
# Create the underlying element and add it to our resulting json
element = _create_base_element('other',
name,
source_mapping,
{},
additional_fields)
d['elements'].append(element)

@ -0,0 +1,454 @@
import os
import json
import logging
from collections import OrderedDict
from slither.core.cfg.node import Node
from slither.core.declarations import Contract, Function, Enum, Event, Structure, Pragma
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.variable import Variable
from slither.exceptions import SlitherError
from slither.utils.colors import yellow
logger = logging.getLogger("Slither")
###################################################################################
###################################################################################
# region Output
###################################################################################
###################################################################################
def output_to_json(filename, error, results):
"""
:param filename: Filename where the json will be written. If None or "-", write to stdout
:param error: Error to report
:param results: Results to report
:param logger: Logger where to log potential info
:return:
"""
# Create our encapsulated JSON result.
json_result = {
"success": error is None,
"error": error,
"results": results
}
if filename == "-":
filename = None
# Determine if we should output to stdout
if filename is None:
# Write json to console
print(json.dumps(json_result))
else:
# Write json to file
if os.path.isfile(filename):
logger.info(yellow(f'{filename} exists already, the overwrite is prevented'))
else:
with open(filename, 'w', encoding='utf8') as f:
json.dump(json_result, f, indent=2)
# endregion
###################################################################################
###################################################################################
# region Json generation
###################################################################################
###################################################################################
def _convert_to_description(d):
if isinstance(d, str):
return d
if not isinstance(d, SourceMapping):
raise SlitherError(f'{d} does not inherit from SourceMapping, conversion impossible')
if isinstance(d, Node):
if d.expression:
return f'{d.expression} ({d.source_mapping_str})'
else:
return f'{str(d)} ({d.source_mapping_str})'
if hasattr(d, 'canonical_name'):
return f'{d.canonical_name} ({d.source_mapping_str})'
if hasattr(d, 'name'):
return f'{d.name} ({d.source_mapping_str})'
raise SlitherError(f'{type(d)} cannot be converted (no name, or canonical_name')
def _convert_to_markdown(d, markdown_root):
if isinstance(d, str):
return d
if not isinstance(d, SourceMapping):
raise SlitherError(f'{d} does not inherit from SourceMapping, conversion impossible')
if isinstance(d, Node):
if d.expression:
return f'[{d.expression}]({d.source_mapping_to_markdown(markdown_root)})'
else:
return f'[{str(d)}]({d.source_mapping_to_markdown(markdown_root)})'
if hasattr(d, 'canonical_name'):
return f'[{d.canonical_name}]({d.source_mapping_to_markdown(markdown_root)})'
if hasattr(d, 'name'):
return f'[{d.name}]({d.source_mapping_to_markdown(markdown_root)})'
raise SlitherError(f'{type(d)} cannot be converted (no name, or canonical_name')
# endregion
###################################################################################
###################################################################################
# region Internal functions
###################################################################################
###################################################################################
def _create_base_element(type, name, source_mapping, type_specific_fields=None, additional_fields=None):
if additional_fields is None:
additional_fields = {}
if type_specific_fields is None:
type_specific_fields = {}
element = {'type': type,
'name': name,
'source_mapping': source_mapping}
if type_specific_fields:
element['type_specific_fields'] = type_specific_fields
if additional_fields:
element['additional_fields'] = additional_fields
return element
def _create_parent_element(element):
from slither.core.children.child_contract import ChildContract
from slither.core.children.child_function import ChildFunction
from slither.core.children.child_inheritance import ChildInheritance
if isinstance(element, ChildInheritance):
if element.contract_declarer:
contract = Output('')
contract.add_contract(element.contract_declarer)
return contract.data['elements'][0]
elif isinstance(element, ChildContract):
if element.contract:
contract = Output('')
contract.add_contract(element.contract)
return contract.data['elements'][0]
elif isinstance(element, ChildFunction):
if element.function:
function = Output('')
function.add_function(element.function)
return function.data['elements'][0]
return None
class Output:
def __init__(self, info, additional_fields=None, markdown_root='', standard_format=True):
if additional_fields is None:
additional_fields = {}
# Allow info to be a string to simplify the API
if isinstance(info, str):
info = [info]
self._data = OrderedDict()
self._data['elements'] = []
self._data['description'] = ''.join(_convert_to_description(d) for d in info)
self._data['markdown'] = ''.join(_convert_to_markdown(d, markdown_root) for d in info)
if standard_format:
to_add = [i for i in info if not isinstance(i, str)]
for add in to_add:
self.add(add)
if additional_fields:
self._data['additional_fields'] = additional_fields
def add(self, add, additional_fields=None):
if isinstance(add, Variable):
self.add_variable(add, additional_fields=additional_fields)
elif isinstance(add, Contract):
self.add_contract(add, additional_fields=additional_fields)
elif isinstance(add, Function):
self.add_function(add, additional_fields=additional_fields)
elif isinstance(add, Enum):
self.add_enum(add, additional_fields=additional_fields)
elif isinstance(add, Event):
self.add_event(add, additional_fields=additional_fields)
elif isinstance(add, Structure):
self.add_struct(add, additional_fields=additional_fields)
elif isinstance(add, Pragma):
self.add_pragma(add, additional_fields=additional_fields)
elif isinstance(add, Node):
self.add_node(add, additional_fields=additional_fields)
else:
raise SlitherError(f'Impossible to add {type(add)} to the json')
@property
def data(self):
return self._data
@property
def elements(self):
return self._data['elements']
# endregion
###################################################################################
###################################################################################
# region Variables
###################################################################################
###################################################################################
def add_variable(self, variable, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'parent': _create_parent_element(variable)
}
element = _create_base_element('variable',
variable.name,
variable.source_mapping,
type_specific_fields,
additional_fields)
self._data['elements'].append(element)
def add_variables_to_output(self, variables):
for variable in sorted(variables, key=lambda x: x.name):
self.add_variable(variable)
# endregion
###################################################################################
###################################################################################
# region Contract
###################################################################################
###################################################################################
def add_contract(self, contract, additional_fields=None):
if additional_fields is None:
additional_fields = {}
element = _create_base_element('contract',
contract.name,
contract.source_mapping,
{},
additional_fields)
self._data['elements'].append(element)
# endregion
###################################################################################
###################################################################################
# region Functions
###################################################################################
###################################################################################
def add_function(self, function, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'parent': _create_parent_element(function),
'signature': function.full_name
}
element = _create_base_element('function',
function.name,
function.source_mapping,
type_specific_fields,
additional_fields)
self._data['elements'].append(element)
def add_functions(self, functions, additional_fields=None):
if additional_fields is None:
additional_fields = {}
for function in sorted(functions, key=lambda x: x.name):
self.add_function(function, additional_fields)
# endregion
###################################################################################
###################################################################################
# region Enum
###################################################################################
###################################################################################
def add_enum(self, enum, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'parent': _create_parent_element(enum)
}
element = _create_base_element('enum',
enum.name,
enum.source_mapping,
type_specific_fields,
additional_fields)
self._data['elements'].append(element)
# endregion
###################################################################################
###################################################################################
# region Structures
###################################################################################
###################################################################################
def add_struct(self, struct, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'parent': _create_parent_element(struct)
}
element = _create_base_element('struct',
struct.name,
struct.source_mapping,
type_specific_fields,
additional_fields)
self._data['elements'].append(element)
# endregion
###################################################################################
###################################################################################
# region Events
###################################################################################
###################################################################################
def add_event(self, event, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'parent': _create_parent_element(event),
'signature': event.full_name
}
element = _create_base_element('event',
event.name,
event.source_mapping,
type_specific_fields,
additional_fields)
self._data['elements'].append(element)
# endregion
###################################################################################
###################################################################################
# region Nodes
###################################################################################
###################################################################################
def add_node(self, node, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'parent': _create_parent_element(node),
}
node_name = str(node.expression) if node.expression else ""
element = _create_base_element('node',
node_name,
node.source_mapping,
type_specific_fields,
additional_fields)
self._data['elements'].append(element)
def add_nodes(self, nodes):
for node in sorted(nodes, key=lambda x: x.node_id):
self.add_node(node)
# endregion
###################################################################################
###################################################################################
# region Pragma
###################################################################################
###################################################################################
def add_pragma(self, pragma, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'directive': pragma.directive
}
element = _create_base_element('pragma',
pragma.version,
pragma.source_mapping,
type_specific_fields,
additional_fields)
self._data['elements'].append(element)
# endregion
###################################################################################
###################################################################################
# region File
###################################################################################
###################################################################################
def add_file(self, filename, content, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'filename': filename,
'content': content
}
element = _create_base_element('file',
type_specific_fields,
additional_fields)
self._data['elements'].append(element)
# endregion
###################################################################################
###################################################################################
# region Pretty Table
###################################################################################
###################################################################################
def add_pretty_table(self, content, name, additional_fields=None):
if additional_fields is None:
additional_fields = {}
type_specific_fields = {
'content': content,
'name': name
}
element = _create_base_element('pretty_table',
type_specific_fields,
additional_fields)
self._data['elements'].append(element)
# endregion
###################################################################################
###################################################################################
# region Others
###################################################################################
###################################################################################
def add_other(self, name, source_mapping, slither, additional_fields=None):
# If this a tuple with (filename, start, end), convert it to a source mapping.
if additional_fields is None:
additional_fields = {}
if isinstance(source_mapping, tuple):
# Parse the source id
(filename, start, end) = source_mapping
source_id = next(
(source_unit_id for (source_unit_id, source_unit_filename) in slither.source_units.items() if
source_unit_filename == filename), -1)
# Convert to a source mapping string
source_mapping = f"{start}:{end}:{source_id}"
# If this is a source mapping string, parse it.
if isinstance(source_mapping, str):
source_mapping_str = source_mapping
source_mapping = SourceMapping()
source_mapping.set_offset(source_mapping_str, slither)
# If this is a source mapping object, get the underlying source mapping dictionary
if isinstance(source_mapping, SourceMapping):
source_mapping = source_mapping.source_mapping
# Create the underlying element and add it to our resulting json
element = _create_base_element('other',
name,
source_mapping,
{},
additional_fields)
self._data['elements'].append(element)
Loading…
Cancel
Save