From 9bbeea58c83a25b4b155ef784cd838e2d5fde2fe Mon Sep 17 00:00:00 2001 From: Ladislav Sladecek Date: Wed, 7 Jul 2021 13:18:24 +0200 Subject: [PATCH] Make --pretty-json work with Standard JSON output --- Changelog.md | 1 + libsolidity/interface/StandardCompiler.cpp | 4 +-- libsolidity/interface/StandardCompiler.h | 9 +++++-- libsolutil/JSON.cpp | 27 +++++++++++++------ libsolutil/JSON.h | 21 +++++++++++++++ solc/CommandLineInterface.cpp | 6 ++--- solc/CommandLineParser.cpp | 21 ++++++++++++--- solc/CommandLineParser.h | 5 +++- test/cmdlineTests.sh | 11 ++++++-- test/cmdlineTests/pretty_json_combined/args | 1 + .../pretty_json_combined/input.sol | 3 +++ test/cmdlineTests/pretty_json_combined/output | 10 +++++++ .../cmdlineTests/pretty_json_indent_only/args | 1 + .../pretty_json_indent_only/input.json | 10 +++++++ .../pretty_json_indent_only/output | 1 + .../pretty_json_indent_only/output.json | 9 +++++++ test/cmdlineTests/pretty_json_standard/args | 1 + .../pretty_json_standard/input.json | 10 +++++++ .../pretty_json_standard/output.json | 9 +++++++ .../pretty_json_standard_indent/args | 1 + .../pretty_json_standard_indent/input.json | 10 +++++++ .../pretty_json_standard_indent/output | 9 +++++++ .../pretty_json_standard_indent/output.json | 9 +++++++ test/solc/CommandLineParser.cpp | 13 ++++++--- 24 files changed, 176 insertions(+), 26 deletions(-) create mode 100644 test/cmdlineTests/pretty_json_combined/args create mode 100644 test/cmdlineTests/pretty_json_combined/input.sol create mode 100644 test/cmdlineTests/pretty_json_combined/output create mode 100644 test/cmdlineTests/pretty_json_indent_only/args create mode 100644 test/cmdlineTests/pretty_json_indent_only/input.json create mode 100644 test/cmdlineTests/pretty_json_indent_only/output create mode 100644 test/cmdlineTests/pretty_json_indent_only/output.json create mode 100644 test/cmdlineTests/pretty_json_standard/args create mode 100644 test/cmdlineTests/pretty_json_standard/input.json create mode 100644 test/cmdlineTests/pretty_json_standard/output.json create mode 100644 test/cmdlineTests/pretty_json_standard_indent/args create mode 100644 test/cmdlineTests/pretty_json_standard_indent/input.json create mode 100644 test/cmdlineTests/pretty_json_standard_indent/output create mode 100644 test/cmdlineTests/pretty_json_standard_indent/output.json diff --git a/Changelog.md b/Changelog.md index 9638d597a..042248fe7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -8,6 +8,7 @@ Compiler Features: * Yul EVM Code Transform: Do not reuse stack slots that immediately become unreachable. * Yul EVM Code Transform: Also pop unused argument slots for functions without return variables (under the same restrictions as for functions with return variables). * Yul Optimizer: Move function arguments and return variables to memory with the experimental Stack Limit Evader (which is not enabled by default). + * Commandline Interface: option ``--pretty-json`` works also with ``--standard--json``. Bugfixes: diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index ab70d93b7..b27e7d08d 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -1420,7 +1420,7 @@ string StandardCompiler::compile(string const& _input) noexcept try { if (!util::jsonParseStrict(_input, input, &errors)) - return util::jsonCompactPrint(formatFatalError("JSONError", errors)); + return util::jsonPrint(formatFatalError("JSONError", errors), m_jsonPrintingFormat); } catch (...) { @@ -1433,7 +1433,7 @@ string StandardCompiler::compile(string const& _input) noexcept try { - return util::jsonCompactPrint(output); + return util::jsonPrint(output, m_jsonPrintingFormat); } catch (...) { diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index da193b3a1..4d43fdfec 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -24,6 +24,7 @@ #pragma once #include +#include #include #include @@ -46,8 +47,10 @@ public: /// Creates a new StandardCompiler. /// @param _readFile callback used to read files for import statements. Must return /// and must not emit exceptions. - explicit StandardCompiler(ReadCallback::Callback _readFile = ReadCallback::Callback()): - m_readFile(std::move(_readFile)) + explicit StandardCompiler(ReadCallback::Callback _readFile = ReadCallback::Callback(), + util::JsonFormat const& _format = {}): + m_readFile(std::move(_readFile)), + m_jsonPrintingFormat(std::move(_format)) { } @@ -91,6 +94,8 @@ private: Json::Value compileYul(InputsAndSettings _inputsAndSettings); ReadCallback::Callback m_readFile; + + util::JsonFormat m_jsonPrintingFormat; }; } diff --git a/libsolutil/JSON.cpp b/libsolutil/JSON.cpp index 81ef7f76b..d27982acb 100644 --- a/libsolutil/JSON.cpp +++ b/libsolutil/JSON.cpp @@ -115,18 +115,29 @@ Json::Value removeNullMembers(Json::Value _json) string jsonPrettyPrint(Json::Value const& _input) { - static map settings{{"indentation", " "}, {"enableYAMLCompatibility", true}}; - static StreamWriterBuilder writerBuilder(settings); - string result = print(_input, writerBuilder); - boost::replace_all(result, " \n", "\n"); - return result; + return jsonPrint(_input, JsonFormat{ JsonFormat::Pretty }); } string jsonCompactPrint(Json::Value const& _input) { - static map settings{{"indentation", ""}}; - static StreamWriterBuilder writerBuilder(settings); - return print(_input, writerBuilder); + return jsonPrint(_input, JsonFormat{ JsonFormat::Compact }); +} + +string jsonPrint(Json::Value const& _input, JsonFormat const& _format) +{ + map settings; + if (_format.format == JsonFormat::Pretty) + { + settings["indentation"] = string(_format.indent, ' '); + settings["enableYAMLCompatibility"] = true; + } + else + settings["indentation"] = ""; + StreamWriterBuilder writerBuilder(settings); + string result = print(_input, writerBuilder); + if (_format.format == JsonFormat::Pretty) + boost::replace_all(result, " \n", "\n"); + return result; } bool jsonParseStrict(string const& _input, Json::Value& _json, string* _errs /* = nullptr */) diff --git a/libsolutil/JSON.h b/libsolutil/JSON.h index c1d045e73..3a326a5e3 100644 --- a/libsolutil/JSON.h +++ b/libsolutil/JSON.h @@ -33,12 +33,33 @@ namespace solidity::util /// Removes members with null value recursively from (@a _json). Json::Value removeNullMembers(Json::Value _json); +/// JSON printing format. +struct JsonFormat +{ + enum Format + { + Compact, + Pretty + }; + + static constexpr uint32_t defaultIndent = 2; + + bool operator==(JsonFormat const& _other) const noexcept { return (format == _other.format) && (indent == _other.indent); } + bool operator!=(JsonFormat const& _other) const noexcept { return !(*this == _other); } + + Format format = Compact; + uint32_t indent = defaultIndent; +}; + /// Serialise the JSON object (@a _input) with indentation std::string jsonPrettyPrint(Json::Value const& _input); /// Serialise the JSON object (@a _input) without indentation std::string jsonCompactPrint(Json::Value const& _input); +/// Serialise the JSON object (@a _input) using specified format (@a _format) +std::string jsonPrint(Json::Value const& _input, JsonFormat const& _format); + /// Parse a JSON string (@a _input) with enabled strict-mode and writes resulting JSON object to (@a _json) /// \param _input JSON input string /// \param _json [out] resulting JSON object diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index aeb40f099..1b7f2d089 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -532,7 +532,7 @@ bool CommandLineInterface::processInput() return false; } } - StandardCompiler compiler(m_fileReader.reader()); + StandardCompiler compiler(m_fileReader.reader(), m_options.formatting.json); sout() << compiler.compile(std::move(input)) << endl; return true; } @@ -771,9 +771,7 @@ void CommandLineInterface::handleCombinedJSON() } } - string json = m_options.formatting.prettyJson ? jsonPrettyPrint(removeNullMembers(std::move(output))) : - jsonCompactPrint(removeNullMembers(std::move(output))); - + string json = jsonPrint(removeNullMembers(std::move(output)), m_options.formatting.json); if (!m_options.output.dir.empty()) createJson("combined", json); else diff --git a/solc/CommandLineParser.cpp b/solc/CommandLineParser.cpp index c09ff906b..adbb1ca38 100644 --- a/solc/CommandLineParser.cpp +++ b/solc/CommandLineParser.cpp @@ -125,6 +125,7 @@ static string const g_strStandardJSON = "standard-json"; static string const g_strStrictAssembly = "strict-assembly"; static string const g_strSwarm = "swarm"; static string const g_strPrettyJson = "pretty-json"; +static string const g_strJsonIndent = "json-indent"; static string const g_strVersion = "version"; static string const g_strIgnoreMissingFiles = "ignore-missing"; static string const g_strColor = "color"; @@ -281,7 +282,7 @@ bool CommandLineOptions::operator==(CommandLineOptions const& _other) const noex assembly.targetMachine == _other.assembly.targetMachine && assembly.inputLanguage == _other.assembly.inputLanguage && linker.libraries == _other.linker.libraries && - formatting.prettyJson == _other.formatting.prettyJson && + formatting.json == _other.formatting.json && formatting.coloredOutput == _other.formatting.coloredOutput && formatting.withErrorIds == _other.formatting.withErrorIds && compiler.outputs == _other.compiler.outputs && @@ -582,7 +583,12 @@ General Information)").c_str(), outputFormatting.add_options() ( g_strPrettyJson.c_str(), - "Output JSON in pretty format. Currently it only works with the combined JSON output." + "Output JSON in pretty format." + ) + ( + g_strJsonIndent.c_str(), + po::value()->value_name("N")->default_value(util::JsonFormat::defaultIndent), + "Indent pretty-printed JSON with N spaces. Enables '--pretty-json' automatically." ) ( g_strColor.c_str(), @@ -790,7 +796,16 @@ General Information)").c_str(), m_options.output.dir = m_args.at(g_strOutputDir).as(); m_options.output.overwriteFiles = (m_args.count(g_strOverwrite) > 0); - m_options.formatting.prettyJson = (m_args.count(g_strPrettyJson) > 0); + + if (m_args.count(g_strPrettyJson) > 0) + { + m_options.formatting.json.format = JsonFormat::Pretty; + } + if (!m_args[g_strJsonIndent].defaulted()) + { + m_options.formatting.json.format = JsonFormat::Pretty; + m_options.formatting.json.indent = m_args[g_strJsonIndent].as(); + } static_assert( sizeof(m_options.compiler.outputs) == 15 * sizeof(bool), diff --git a/solc/CommandLineParser.h b/solc/CommandLineParser.h index b05caa705..539379216 100644 --- a/solc/CommandLineParser.h +++ b/solc/CommandLineParser.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -101,6 +102,7 @@ struct CommandLineOptions bool operator==(CommandLineOptions const& _other) const noexcept; bool operator!=(CommandLineOptions const& _other) const noexcept { return !(*this == _other); } + struct { InputMode mode = InputMode::Compiler; @@ -137,7 +139,7 @@ struct CommandLineOptions struct { - bool prettyJson = false; + util::JsonFormat json; std::optional coloredOutput; bool withErrorIds = false; } formatting; @@ -168,6 +170,7 @@ struct CommandLineOptions bool initialize = false; ModelCheckerSettings settings; } modelChecker; + }; /// Parses the command-line arguments and produces a filled-out CommandLineOptions structure. diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index a864ce17a..7deaa0c32 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -141,9 +141,16 @@ function test_solc_behaviour() if [[ " ${solc_args[*]} " == *" --standard-json "* ]] then - sed -i.bak -e 's/{[^{]*Warning: This is a pre-release compiler version[^}]*},\{0,1\}//' "$stdout_path" + python3 - < }] +json = re.sub(r"\"errors\":\s*\[\s*\],?\s*","",json) # Remove "errors" array if it's not empty +json = re.sub("\n\\s+\n", "\n\n", json) # Remove any leftover trailing whitespace +open("$stdout_path", "w").write(json) +EOF sed -i.bak -E -e 's/ Consider adding \\"pragma solidity \^[0-9.]*;\\"//g' "$stdout_path" - sed -i.bak -e 's/"errors":\[\],\{0,1\}//' "$stdout_path" sed -i.bak -E -e 's/\"opcodes\":\"[^"]+\"/\"opcodes\":\"\"/g' "$stdout_path" sed -i.bak -E -e 's/\"sourceMap\":\"[0-9:;-]+\"/\"sourceMap\":\"\"/g' "$stdout_path" diff --git a/test/cmdlineTests/pretty_json_combined/args b/test/cmdlineTests/pretty_json_combined/args new file mode 100644 index 000000000..f2ed65c2e --- /dev/null +++ b/test/cmdlineTests/pretty_json_combined/args @@ -0,0 +1 @@ +--combined-json abi --pretty-json --json-indent 3 diff --git a/test/cmdlineTests/pretty_json_combined/input.sol b/test/cmdlineTests/pretty_json_combined/input.sol new file mode 100644 index 000000000..625af5682 --- /dev/null +++ b/test/cmdlineTests/pretty_json_combined/input.sol @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; +contract C {} diff --git a/test/cmdlineTests/pretty_json_combined/output b/test/cmdlineTests/pretty_json_combined/output new file mode 100644 index 000000000..6bed3b94d --- /dev/null +++ b/test/cmdlineTests/pretty_json_combined/output @@ -0,0 +1,10 @@ +{ + "contracts": + { + "pretty_json_combined/input.sol:C": + { + "abi": [] + } + }, + "version": "" +} diff --git a/test/cmdlineTests/pretty_json_indent_only/args b/test/cmdlineTests/pretty_json_indent_only/args new file mode 100644 index 000000000..10c94a50c --- /dev/null +++ b/test/cmdlineTests/pretty_json_indent_only/args @@ -0,0 +1 @@ +--json-indent 7 diff --git a/test/cmdlineTests/pretty_json_indent_only/input.json b/test/cmdlineTests/pretty_json_indent_only/input.json new file mode 100644 index 000000000..e9dc4cf38 --- /dev/null +++ b/test/cmdlineTests/pretty_json_indent_only/input.json @@ -0,0 +1,10 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.0; contract C {}" + } + } +} diff --git a/test/cmdlineTests/pretty_json_indent_only/output b/test/cmdlineTests/pretty_json_indent_only/output new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/test/cmdlineTests/pretty_json_indent_only/output @@ -0,0 +1 @@ + diff --git a/test/cmdlineTests/pretty_json_indent_only/output.json b/test/cmdlineTests/pretty_json_indent_only/output.json new file mode 100644 index 000000000..80d51c4f1 --- /dev/null +++ b/test/cmdlineTests/pretty_json_indent_only/output.json @@ -0,0 +1,9 @@ +{ + "sources": + { + "A": + { + "id": 0 + } + } +} diff --git a/test/cmdlineTests/pretty_json_standard/args b/test/cmdlineTests/pretty_json_standard/args new file mode 100644 index 000000000..d13a8ac44 --- /dev/null +++ b/test/cmdlineTests/pretty_json_standard/args @@ -0,0 +1 @@ +--pretty-json diff --git a/test/cmdlineTests/pretty_json_standard/input.json b/test/cmdlineTests/pretty_json_standard/input.json new file mode 100644 index 000000000..e9dc4cf38 --- /dev/null +++ b/test/cmdlineTests/pretty_json_standard/input.json @@ -0,0 +1,10 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.0; contract C {}" + } + } +} diff --git a/test/cmdlineTests/pretty_json_standard/output.json b/test/cmdlineTests/pretty_json_standard/output.json new file mode 100644 index 000000000..29185e758 --- /dev/null +++ b/test/cmdlineTests/pretty_json_standard/output.json @@ -0,0 +1,9 @@ +{ + "sources": + { + "A": + { + "id": 0 + } + } +} diff --git a/test/cmdlineTests/pretty_json_standard_indent/args b/test/cmdlineTests/pretty_json_standard_indent/args new file mode 100644 index 000000000..54a84919f --- /dev/null +++ b/test/cmdlineTests/pretty_json_standard_indent/args @@ -0,0 +1 @@ +--pretty-json --json-indent 7 diff --git a/test/cmdlineTests/pretty_json_standard_indent/input.json b/test/cmdlineTests/pretty_json_standard_indent/input.json new file mode 100644 index 000000000..df5bb3dfc --- /dev/null +++ b/test/cmdlineTests/pretty_json_standard_indent/input.json @@ -0,0 +1,10 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity >=0.0; contract C { }" + } + } +} diff --git a/test/cmdlineTests/pretty_json_standard_indent/output b/test/cmdlineTests/pretty_json_standard_indent/output new file mode 100644 index 000000000..80d51c4f1 --- /dev/null +++ b/test/cmdlineTests/pretty_json_standard_indent/output @@ -0,0 +1,9 @@ +{ + "sources": + { + "A": + { + "id": 0 + } + } +} diff --git a/test/cmdlineTests/pretty_json_standard_indent/output.json b/test/cmdlineTests/pretty_json_standard_indent/output.json new file mode 100644 index 000000000..80d51c4f1 --- /dev/null +++ b/test/cmdlineTests/pretty_json_standard_indent/output.json @@ -0,0 +1,9 @@ +{ + "sources": + { + "A": + { + "id": 0 + } + } +} diff --git a/test/solc/CommandLineParser.cpp b/test/solc/CommandLineParser.cpp index 430399c5c..015c28b98 100644 --- a/test/solc/CommandLineParser.cpp +++ b/test/solc/CommandLineParser.cpp @@ -138,6 +138,7 @@ BOOST_AUTO_TEST_CASE(cli_mode_options) "--experimental-via-ir", "--revert-strings=strip", "--pretty-json", + "--json-indent=7", "--no-color", "--error-codes", "--libraries=" @@ -173,6 +174,7 @@ BOOST_AUTO_TEST_CASE(cli_mode_options) {"a", "b", "c/d"}, {"", "contract.sol", ""}, }; + expectedOptions.input.addStdin = true; expectedOptions.input.basePath = "/home/user/"; expectedOptions.input.allowedDirectories = {"/tmp", "/home", "project", "../contracts", "", "c", "/usr/lib"}; @@ -183,11 +185,11 @@ BOOST_AUTO_TEST_CASE(cli_mode_options) expectedOptions.output.evmVersion = EVMVersion::spuriousDragon(); expectedOptions.output.experimentalViaIR = true; expectedOptions.output.revertStrings = RevertStrings::Strip; + expectedOptions.formatting.json = JsonFormat{JsonFormat::Pretty, 7}; expectedOptions.linker.libraries = { {"dir1/file1.sol:L", h160("1234567890123456789012345678901234567890")}, {"dir2/file2.sol:L", h160("1111122222333334444455555666667777788888")}, }; - expectedOptions.formatting.prettyJson = true; expectedOptions.formatting.coloredOutput = false; expectedOptions.formatting.withErrorIds = true; expectedOptions.compiler.outputs = { @@ -269,6 +271,7 @@ BOOST_AUTO_TEST_CASE(assembly_mode_options) "--experimental-via-ir", // Ignored in assembly mode "--revert-strings=strip", // Accepted but has no effect in assembly mode "--pretty-json", + "--json-indent=1", "--no-color", "--error-codes", "--libraries=" @@ -313,13 +316,13 @@ BOOST_AUTO_TEST_CASE(assembly_mode_options) expectedOptions.output.overwriteFiles = true; expectedOptions.output.evmVersion = EVMVersion::spuriousDragon(); expectedOptions.output.revertStrings = RevertStrings::Strip; + expectedOptions.formatting.json = JsonFormat {JsonFormat::Pretty, 1}; expectedOptions.assembly.targetMachine = expectedMachine; expectedOptions.assembly.inputLanguage = expectedLanguage; expectedOptions.linker.libraries = { {"dir1/file1.sol:L", h160("1234567890123456789012345678901234567890")}, {"dir2/file2.sol:L", h160("1111122222333334444455555666667777788888")}, }; - expectedOptions.formatting.prettyJson = true; expectedOptions.formatting.coloredOutput = false; expectedOptions.formatting.withErrorIds = true; expectedOptions.compiler.outputs = { @@ -359,7 +362,8 @@ BOOST_AUTO_TEST_CASE(standard_json_mode_options) "--evm-version=spuriousDragon", // Ignored in Standard JSON mode "--experimental-via-ir", // Ignored in Standard JSON mode "--revert-strings=strip", // Accepted but has no effect in Standard JSON mode - "--pretty-json", // Accepted but has no effect in Standard JSON mode + "--pretty-json", + "--json-indent=1", "--no-color", // Accepted but has no effect in Standard JSON mode "--error-codes", // Accepted but has no effect in Standard JSON mode "--libraries=" // Ignored in Standard JSON mode @@ -387,6 +391,7 @@ BOOST_AUTO_TEST_CASE(standard_json_mode_options) }; CommandLineOptions expectedOptions; + expectedOptions.input.mode = InputMode::StandardJson; expectedOptions.input.paths = {}; expectedOptions.input.standardJsonFile = "input.json"; @@ -395,7 +400,7 @@ BOOST_AUTO_TEST_CASE(standard_json_mode_options) expectedOptions.output.dir = "/tmp/out"; expectedOptions.output.overwriteFiles = true; expectedOptions.output.revertStrings = RevertStrings::Strip; - expectedOptions.formatting.prettyJson = true; + expectedOptions.formatting.json = JsonFormat {JsonFormat::Pretty, 1}; expectedOptions.formatting.coloredOutput = false; expectedOptions.formatting.withErrorIds = true; expectedOptions.compiler.outputs = {