From f70e89bf12c1d5211050ea2b7de7bc785af42778 Mon Sep 17 00:00:00 2001 From: Josselin Date: Tue, 12 Nov 2019 10:19:30 +0100 Subject: [PATCH] Create utils.output.Output class to handle json output Remove add*_json function --- .../slither_my_plugin/detectors/example.py | 2 +- slither/__main__.py | 4 +- slither/detectors/abstract_detector.py | 72 +-- .../detectors/attributes/const_functions.py | 8 +- .../detectors/attributes/constant_pragma.py | 4 +- .../detectors/attributes/incorrect_solc.py | 2 +- slither/detectors/attributes/locked_ether.py | 2 +- .../erc/incorrect_erc20_interface.py | 2 +- .../erc/incorrect_erc721_interface.py | 4 +- .../erc/unindexed_event_parameters.py | 8 +- slither/detectors/examples/backdoor.py | 4 +- slither/detectors/functions/arbitrary_send.py | 4 +- .../detectors/functions/complex_function.py | 7 +- .../detectors/functions/external_function.py | 4 +- slither/detectors/functions/suicidal.py | 4 +- .../naming_convention/naming_convention.py | 60 +-- .../detectors/operations/block_timestamp.py | 4 +- .../detectors/operations/low_level_calls.py | 4 +- .../operations/unused_return_values.py | 4 +- .../detectors/operations/void_constructor.py | 4 +- .../detectors/reentrancy/reentrancy_benign.py | 12 +- .../detectors/reentrancy/reentrancy_eth.py | 12 +- .../reentrancy_read_before_write.py | 10 +- slither/detectors/shadowing/abstract.py | 4 +- .../detectors/shadowing/builtin_symbols.py | 4 +- slither/detectors/shadowing/local.py | 4 +- slither/detectors/shadowing/state.py | 5 +- slither/detectors/source/rtlo.py | 11 +- slither/detectors/statements/assembly.py | 4 +- slither/detectors/statements/calls_in_loop.py | 4 +- .../statements/controlled_delegatecall.py | 4 +- .../detectors/statements/deprecated_calls.py | 4 +- .../statements/incorrect_strict_equality.py | 4 +- .../detectors/statements/too_many_digits.py | 4 +- slither/detectors/statements/tx_origin.py | 5 +- .../possible_const_state_variables.py | 2 +- .../uninitialized_local_variables.py | 2 +- .../uninitialized_state_variables.py | 2 +- .../uninitialized_storage_variables.py | 2 +- .../variables/unused_state_variables.py | 2 +- slither/printers/abstract_printer.py | 32 +- slither/printers/call/call_graph.py | 6 +- slither/printers/functions/authorization.py | 6 +- slither/printers/functions/cfg.py | 6 +- slither/printers/guidance/echidna.py | 4 +- slither/printers/inheritance/inheritance.py | 4 +- .../printers/inheritance/inheritance_graph.py | 6 +- slither/printers/summary/constructor_calls.py | 83 ++-- slither/printers/summary/contract.py | 15 +- slither/printers/summary/data_depenency.py | 6 +- slither/printers/summary/function.py | 6 +- slither/printers/summary/function_ids.py | 6 +- slither/printers/summary/human_summary.py | 9 +- slither/printers/summary/modifier_calls.py | 6 +- slither/printers/summary/require_calls.py | 6 +- slither/printers/summary/slithir.py | 32 +- slither/printers/summary/slithir_ssa.py | 22 +- slither/printers/summary/variable_order.py | 7 +- slither/slither.py | 2 +- slither/tools/erc_conformance/__main__.py | 6 +- slither/tools/erc_conformance/erc/erc20.py | 8 +- slither/tools/erc_conformance/erc/ercs.py | 57 ++- slither/tools/kspec_coverage/analysis.py | 43 +- slither/tools/upgradeability/__main__.py | 12 +- .../upgradeability/check_initialization.py | 28 +- .../check_variable_initialization.py | 8 +- .../upgradeability/compare_function_ids.py | 32 +- .../upgradeability/compare_variables_order.py | 22 +- .../tools/upgradeability/constant_checks.py | 26 +- slither/utils/json_utils.py | 452 ----------------- slither/utils/output.py | 454 ++++++++++++++++++ 71 files changed, 826 insertions(+), 894 deletions(-) delete mode 100644 slither/utils/json_utils.py create mode 100644 slither/utils/output.py diff --git a/plugin_example/slither_my_plugin/detectors/example.py b/plugin_example/slither_my_plugin/detectors/example.py index ed850d361..5d5a8811e 100644 --- a/plugin_example/slither_my_plugin/detectors/example.py +++ b/plugin_example/slither_my_plugin/detectors/example.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] diff --git a/slither/__main__.py b/slither/__main__.py index 699134c58..e982b8cde 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -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: diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index 06a6ae8fa..765b79d15 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -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) + def generate_result(self, info, additional_fields=None): + output = Output(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] + output.data['check'] = self.ARGUMENT + output.data['impact'] = classification_txt[self.IMPACT] + output.data['confidence'] = classification_txt[self.CONFIDENCE] - 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) - - @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): diff --git a/slither/detectors/attributes/const_functions.py b/slither/detectors/attributes/const_functions.py index 99247b91b..6e892a626 100644 --- a/slither/detectors/attributes/const_functions.py +++ b/slither/detectors/attributes/const_functions.py @@ -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 diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index d931d6a3d..b7d593357 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -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 diff --git a/slither/detectors/attributes/incorrect_solc.py b/slither/detectors/attributes/incorrect_solc.py index 6a48bc3c7..f63a2bdf2 100644 --- a/slither/detectors/attributes/incorrect_solc.py +++ b/slither/detectors/attributes/incorrect_solc.py @@ -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) diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index 3de56c00c..97ffb0db2 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -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) diff --git a/slither/detectors/erc/incorrect_erc20_interface.py b/slither/detectors/erc/incorrect_erc20_interface.py index a9aa4ad77..b7782a66a 100644 --- a/slither/detectors/erc/incorrect_erc20_interface.py +++ b/slither/detectors/erc/incorrect_erc20_interface.py @@ -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) diff --git a/slither/detectors/erc/incorrect_erc721_interface.py b/slither/detectors/erc/incorrect_erc721_interface.py index bce1695b1..e6a484631 100644 --- a/slither/detectors/erc/incorrect_erc721_interface.py +++ b/slither/detectors/erc/incorrect_erc721_interface.py @@ -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 diff --git a/slither/detectors/erc/unindexed_event_parameters.py b/slither/detectors/erc/unindexed_event_parameters.py index 1e382da59..096c63f16 100644 --- a/slither/detectors/erc/unindexed_event_parameters.py +++ b/slither/detectors/erc/unindexed_event_parameters.py @@ -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 diff --git a/slither/detectors/examples/backdoor.py b/slither/detectors/examples/backdoor.py index d9f00979e..76511d018 100644 --- a/slither/detectors/examples/backdoor.py +++ b/slither/detectors/examples/backdoor.py @@ -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 diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index 4a14873a9..2c608e73a 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -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 diff --git a/slither/detectors/functions/complex_function.py b/slither/detectors/functions/complex_function.py index fae9a0923..21add9239 100644 --- a/slither/detectors/functions/complex_function.py +++ b/slither/detectors/functions/complex_function.py @@ -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 diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 7b48be5ba..f5d9aad61 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -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 diff --git a/slither/detectors/functions/suicidal.py b/slither/detectors/functions/suicidal.py index 2dc28cf13..a3599fcad 100644 --- a/slither/detectors/functions/suicidal.py +++ b/slither/detectors/functions/suicidal.py @@ -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 diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 93825e419..3f07d1cba 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -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 diff --git a/slither/detectors/operations/block_timestamp.py b/slither/detectors/operations/block_timestamp.py index 23ad8e178..6dc76ae50 100644 --- a/slither/detectors/operations/block_timestamp.py +++ b/slither/detectors/operations/block_timestamp.py @@ -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 diff --git a/slither/detectors/operations/low_level_calls.py b/slither/detectors/operations/low_level_calls.py index c22ff8139..1a46a9909 100644 --- a/slither/detectors/operations/low_level_calls.py +++ b/slither/detectors/operations/low_level_calls.py @@ -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 diff --git a/slither/detectors/operations/unused_return_values.py b/slither/detectors/operations/unused_return_values.py index a8233a6d8..b1e2b6d08 100644 --- a/slither/detectors/operations/unused_return_values.py +++ b/slither/detectors/operations/unused_return_values.py @@ -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 diff --git a/slither/detectors/operations/void_constructor.py b/slither/detectors/operations/void_constructor.py index 181a5ae50..74abd1329 100644 --- a/slither/detectors/operations/void_constructor.py +++ b/slither/detectors/operations/void_constructor.py @@ -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 diff --git a/slither/detectors/reentrancy/reentrancy_benign.py b/slither/detectors/reentrancy/reentrancy_benign.py index 79dfec78f..2ff6e8475 100644 --- a/slither/detectors/reentrancy/reentrancy_benign.py +++ b/slither/detectors/reentrancy/reentrancy_benign.py @@ -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 diff --git a/slither/detectors/reentrancy/reentrancy_eth.py b/slither/detectors/reentrancy/reentrancy_eth.py index b0d501ec1..a5f0c0365 100644 --- a/slither/detectors/reentrancy/reentrancy_eth.py +++ b/slither/detectors/reentrancy/reentrancy_eth.py @@ -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 diff --git a/slither/detectors/reentrancy/reentrancy_read_before_write.py b/slither/detectors/reentrancy/reentrancy_read_before_write.py index 6389847fc..f28b5b615 100644 --- a/slither/detectors/reentrancy/reentrancy_read_before_write.py +++ b/slither/detectors/reentrancy/reentrancy_read_before_write.py @@ -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 diff --git a/slither/detectors/shadowing/abstract.py b/slither/detectors/shadowing/abstract.py index f2e7c7119..d882a09a3 100644 --- a/slither/detectors/shadowing/abstract.py +++ b/slither/detectors/shadowing/abstract.py @@ -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 diff --git a/slither/detectors/shadowing/builtin_symbols.py b/slither/detectors/shadowing/builtin_symbols.py index c0bf4e8b0..1af228c5a 100644 --- a/slither/detectors/shadowing/builtin_symbols.py +++ b/slither/detectors/shadowing/builtin_symbols.py @@ -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 diff --git a/slither/detectors/shadowing/local.py b/slither/detectors/shadowing/local.py index 0da45cf99..7f00f495d 100644 --- a/slither/detectors/shadowing/local.py +++ b/slither/detectors/shadowing/local.py @@ -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 diff --git a/slither/detectors/shadowing/state.py b/slither/detectors/shadowing/state.py index b5df61151..81421cd2d 100644 --- a/slither/detectors/shadowing/state.py +++ b/slither/detectors/shadowing/state.py @@ -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 diff --git a/slither/detectors/source/rtlo.py b/slither/detectors/source/rtlo.py index 135b5aa6a..d69201334 100644 --- a/slither/detectors/source/rtlo.py +++ b/slither/detectors/source/rtlo.py @@ -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 diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py index af65f2331..bf84dab7a 100644 --- a/slither/detectors/statements/assembly.py +++ b/slither/detectors/statements/assembly.py @@ -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 diff --git a/slither/detectors/statements/calls_in_loop.py b/slither/detectors/statements/calls_in_loop.py index b2dfd5c11..cf115c858 100644 --- a/slither/detectors/statements/calls_in_loop.py +++ b/slither/detectors/statements/calls_in_loop.py @@ -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 diff --git a/slither/detectors/statements/controlled_delegatecall.py b/slither/detectors/statements/controlled_delegatecall.py index 0dcff87bc..987757847 100644 --- a/slither/detectors/statements/controlled_delegatecall.py +++ b/slither/detectors/statements/controlled_delegatecall.py @@ -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 diff --git a/slither/detectors/statements/deprecated_calls.py b/slither/detectors/statements/deprecated_calls.py index de8b8e860..b56a807c5 100644 --- a/slither/detectors/statements/deprecated_calls.py +++ b/slither/detectors/statements/deprecated_calls.py @@ -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 diff --git a/slither/detectors/statements/incorrect_strict_equality.py b/slither/detectors/statements/incorrect_strict_equality.py index 01faf4d62..b0b47d025 100644 --- a/slither/detectors/statements/incorrect_strict_equality.py +++ b/slither/detectors/statements/incorrect_strict_equality.py @@ -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 diff --git a/slither/detectors/statements/too_many_digits.py b/slither/detectors/statements/too_many_digits.py index 0e6591546..32d4ab93c 100644 --- a/slither/detectors/statements/too_many_digits.py +++ b/slither/detectors/statements/too_many_digits.py @@ -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 diff --git a/slither/detectors/statements/tx_origin.py b/slither/detectors/statements/tx_origin.py index 8f2b9968b..adc274b10 100644 --- a/slither/detectors/statements/tx_origin.py +++ b/slither/detectors/statements/tx_origin.py @@ -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 diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py index eaed2dac8..2d6d40b06 100644 --- a/slither/detectors/variables/possible_const_state_variables.py +++ b/slither/detectors/variables/possible_const_state_variables.py @@ -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 diff --git a/slither/detectors/variables/uninitialized_local_variables.py b/slither/detectors/variables/uninitialized_local_variables.py index cc0ff0968..09d185208 100644 --- a/slither/detectors/variables/uninitialized_local_variables.py +++ b/slither/detectors/variables/uninitialized_local_variables.py @@ -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 diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 8eaa784e6..795d74e39 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -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 diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index f77f95d2a..7efa3391b 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -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 diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index def3d2804..d1b4f2179 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -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 diff --git a/slither/printers/abstract_printer.py b/slither/printers/abstract_printer.py index 3c7331138..ceaa75d5d 100644 --- a/slither/printers/abstract_printer.py +++ b/slither/printers/abstract_printer.py @@ -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""" diff --git a/slither/printers/call/call_graph.py b/slither/printers/call/call_graph.py index 2f224ece0..8710e82ed 100644 --- a/slither/printers/call/call_graph.py +++ b/slither/printers/call/call_graph.py @@ -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 diff --git a/slither/printers/functions/authorization.py b/slither/printers/functions/authorization.py index 72fc532b3..24f606f1a 100644 --- a/slither/printers/functions/authorization.py +++ b/slither/printers/functions/authorization.py @@ -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 \ No newline at end of file + return res \ No newline at end of file diff --git a/slither/printers/functions/cfg.py b/slither/printers/functions/cfg.py index 191d63b5c..a8b53a3be 100644 --- a/slither/printers/functions/cfg.py +++ b/slither/printers/functions/cfg.py @@ -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 \ No newline at end of file + res.add_file(filename, content) + return res \ No newline at end of file diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index f2034aa17..ab17cd15b 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -140,4 +140,6 @@ class Echidna(AbstractPrinter): self.info(json.dumps(d, indent=4)) - return d \ No newline at end of file + res = self.generate_output(json.dumps(d, indent=4)) + + return res \ No newline at end of file diff --git a/slither/printers/inheritance/inheritance.py b/slither/printers/inheritance/inheritance.py index f3b970dfc..e6b2972fb 100644 --- a/slither/printers/inheritance/inheritance.py +++ b/slither/printers/inheritance/inheritance.py @@ -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 diff --git a/slither/printers/inheritance/inheritance_graph.py b/slither/printers/inheritance/inheritance_graph.py index 52f4f32e2..de527809a 100644 --- a/slither/printers/inheritance/inheritance_graph.py +++ b/slither/printers/inheritance/inheritance_graph.py @@ -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 \ No newline at end of file + return res \ No newline at end of file diff --git a/slither/printers/summary/constructor_calls.py b/slither/printers/summary/constructor_calls.py index d0edb93be..aae307827 100644 --- a/slither/printers/summary/constructor_calls.py +++ b/slither/printers/summary/constructor_calls.py @@ -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' - - 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] + 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 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 diff --git a/slither/printers/summary/contract.py b/slither/printers/summary/contract.py index df7742281..e8f91d897 100644 --- a/slither/printers/summary/contract.py +++ b/slither/printers/summary/contract.py @@ -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 diff --git a/slither/printers/summary/data_depenency.py b/slither/printers/summary/data_depenency.py index cbd36fcdb..7e657ca0b 100644 --- a/slither/printers/summary/data_depenency.py +++ b/slither/printers/summary/data_depenency.py @@ -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 diff --git a/slither/printers/summary/function.py b/slither/printers/summary/function.py index e9a71a965..f144c9736 100644 --- a/slither/printers/summary/function.py +++ b/slither/printers/summary/function.py @@ -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 diff --git a/slither/printers/summary/function_ids.py b/slither/printers/summary/function_ids.py index 5aee6529c..dc1560e5d 100644 --- a/slither/printers/summary/function_ids.py +++ b/slither/printers/summary/function_ids.py @@ -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 \ No newline at end of file + return res \ No newline at end of file diff --git a/slither/printers/summary/human_summary.py b/slither/printers/summary/human_summary.py index 156e353fa..9c986065d 100644 --- a/slither/printers/summary/human_summary.py +++ b/slither/printers/summary/human_summary.py @@ -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 diff --git a/slither/printers/summary/modifier_calls.py b/slither/printers/summary/modifier_calls.py index fbc62c90d..4d3cb7182 100644 --- a/slither/printers/summary/modifier_calls.py +++ b/slither/printers/summary/modifier_calls.py @@ -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 \ No newline at end of file + return res \ No newline at end of file diff --git a/slither/printers/summary/require_calls.py b/slither/printers/summary/require_calls.py index 51c67284d..25ae4af74 100644 --- a/slither/printers/summary/require_calls.py +++ b/slither/printers/summary/require_calls.py @@ -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 diff --git a/slither/printers/summary/slithir.py b/slither/printers/summary/slithir.py index 3466d0212..f75c8b062 100644 --- a/slither/printers/summary/slithir.py +++ b/slither/printers/summary/slithir.py @@ -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 diff --git a/slither/printers/summary/slithir_ssa.py b/slither/printers/summary/slithir_ssa.py index 37acd457e..26d962401 100644 --- a/slither/printers/summary/slithir_ssa.py +++ b/slither/printers/summary/slithir_ssa.py @@ -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 diff --git a/slither/printers/summary/variable_order.py b/slither/printers/summary/variable_order.py index ffee1f940..1e78cf1df 100644 --- a/slither/printers/summary/variable_order.py +++ b/slither/printers/summary/variable_order.py @@ -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 \ No newline at end of file + res.add_pretty_table(table, name) + return res \ No newline at end of file diff --git a/slither/slither.py b/slither/slither.py index 1dbf9e7ce..55698de74 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -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): diff --git a/slither/tools/erc_conformance/__main__.py b/slither/tools/erc_conformance/__main__.py index f581db047..fe0fbb845 100644 --- a/slither/tools/erc_conformance/__main__.py +++ b/slither/tools/erc_conformance/__main__.py @@ -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__': diff --git a/slither/tools/erc_conformance/erc/erc20.py b/slither/tools/erc_conformance/erc/erc20.py index 3143ed34d..25473bc84 100644 --- a/slither/tools/erc_conformance/erc/erc20.py +++ b/slither/tools/erc_conformance/erc/erc20.py @@ -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): diff --git a/slither/tools/erc_conformance/erc/ercs.py b/slither/tools/erc_conformance/erc/ercs.py index 523609263..5af000357 100644 --- a/slither/tools/erc_conformance/erc/ercs.py +++ b/slither/tools/erc_conformance/erc/ercs.py @@ -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): diff --git a/slither/tools/kspec_coverage/analysis.py b/slither/tools/kspec_coverage/analysis.py index 6ca9f9a83..d2daf03d0 100755 --- a/slither/tools/kspec_coverage/analysis.py +++ b/slither/tools/kspec_coverage/analysis.py @@ -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() @@ -56,9 +58,10 @@ def _get_slither_functions(slither): # TODO: integrate state variables all_functions_declared += list(set([s for s in slither.state_variables if s.visibility in ['public', 'external']])) slither_functions = {(function.contract.name, function.full_name): function for function in all_functions_declared} - + 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,23 +117,24 @@ 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, args.json) - + # 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)). if ',' in kspec: @@ -143,4 +147,3 @@ def run_analysis(args, slither, kspec): # Run coverage analysis _run_coverage_analysis(args, slither, kspec_functions) - diff --git a/slither/tools/upgradeability/__main__.py b/slither/tools/upgradeability/__main__.py index 305a43dae..df43a7dfb 100644 --- a/slither/tools/upgradeability/__main__.py +++ b/slither/tools/upgradeability/__main__.py @@ -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 diff --git a/slither/tools/upgradeability/check_initialization.py b/slither/tools/upgradeability/check_initialization.py index 446468389..4fe07801a 100644 --- a/slither/tools/upgradeability/check_initialization.py +++ b/slither/tools/upgradeability/check_initialization.py @@ -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')) diff --git a/slither/tools/upgradeability/check_variable_initialization.py b/slither/tools/upgradeability/check_variable_initialization.py index cedfbc314..cb2473f67 100644 --- a/slither/tools/upgradeability/check_variable_initialization.py +++ b/slither/tools/upgradeability/check_variable_initialization.py @@ -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: diff --git a/slither/tools/upgradeability/compare_function_ids.py b/slither/tools/upgradeability/compare_function_ids.py index da7eda61e..34b63d8bf 100644 --- a/slither/tools/upgradeability/compare_function_ids.py +++ b/slither/tools/upgradeability/compare_function_ids.py @@ -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')) diff --git a/slither/tools/upgradeability/compare_variables_order.py b/slither/tools/upgradeability/compare_variables_order.py index 3b8fd9c30..ea5852d3f 100644 --- a/slither/tools/upgradeability/compare_variables_order.py +++ b/slither/tools/upgradeability/compare_variables_order.py @@ -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: diff --git a/slither/tools/upgradeability/constant_checks.py b/slither/tools/upgradeability/constant_checks.py index d66b41fc2..e25924350 100644 --- a/slither/tools/upgradeability/constant_checks.py +++ b/slither/tools/upgradeability/constant_checks.py @@ -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 diff --git a/slither/utils/json_utils.py b/slither/utils/json_utils.py deleted file mode 100644 index 3ae2b8862..000000000 --- a/slither/utils/json_utils.py +++ /dev/null @@ -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) diff --git a/slither/utils/output.py b/slither/utils/output.py new file mode 100644 index 000000000..cbceef467 --- /dev/null +++ b/slither/utils/output.py @@ -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)