From 58d82439212969168a323406852f35cb6735a19c Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Wed, 10 Jul 2019 17:11:03 +0200 Subject: [PATCH] isoltest: Fixes parsing and printing strings with *basic* escape sequences in it. We explicitly did not implement a fully conformant ANSI escape sequence parser but only what is needed for now. --- test/libsolidity/util/BytesUtils.cpp | 27 +++++--- test/libsolidity/util/BytesUtils.h | 7 +- test/libsolidity/util/TestFileParser.cpp | 68 ++++++++++++++++++- test/libsolidity/util/TestFileParser.h | 5 +- test/libsolidity/util/TestFileParserTests.cpp | 44 ++++++++++++ test/libsolidity/util/TestFunctionCall.cpp | 2 +- 6 files changed, 136 insertions(+), 17 deletions(-) diff --git a/test/libsolidity/util/BytesUtils.cpp b/test/libsolidity/util/BytesUtils.cpp index dfaa76d06..6c37511a0 100644 --- a/test/libsolidity/util/BytesUtils.cpp +++ b/test/libsolidity/util/BytesUtils.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -141,22 +142,28 @@ string BytesUtils::formatHexString(bytes const& _bytes) const return os.str(); } -string BytesUtils::formatString(bytes const& _bytes) const +string BytesUtils::formatString(bytes const& _bytes, size_t _cutOff) const { stringstream os; os << "\""; - bool expectZeros = false; - for (auto const& v: _bytes) + for (size_t i = 0; i < min(_cutOff, _bytes.size()); ++i) { - if (expectZeros && v != 0) - return {}; - if (v == 0) expectZeros = true; - else + auto const v = _bytes[i]; + switch (v) { - if (!isprint(v) || v == '"') - return {}; - os << v; + case '\0': + os << "\\0"; + break; + case '\n': + os << "\\n"; + break; + default: + if (isprint(v)) + os << v; + else + os << "\\x" << setw(2) << setfill('0') << hex << v; + } } os << "\""; diff --git a/test/libsolidity/util/BytesUtils.h b/test/libsolidity/util/BytesUtils.h index c090e179e..8b2792692 100644 --- a/test/libsolidity/util/BytesUtils.h +++ b/test/libsolidity/util/BytesUtils.h @@ -76,7 +76,12 @@ public: /// Converts \param _bytes to a soltest-compliant and human-readable /// string representation of a byte array which is assumed to hold /// a string value. - std::string formatString(bytes const& _bytes) const; + std::string formatString(bytes const& _bytes, size_t _cutOff) const; + + std::string formatString(bytes const& _bytes) const + { + return formatString(_bytes, _bytes.size()); + } /// Left-aligns and pads given _bytes and returns a new /// bytes array. diff --git a/test/libsolidity/util/TestFileParser.cpp b/test/libsolidity/util/TestFileParser.cpp index cb71c2c95..c24c0450c 100644 --- a/test/libsolidity/util/TestFileParser.cpp +++ b/test/libsolidity/util/TestFileParser.cpp @@ -300,8 +300,8 @@ Parameter TestFileParser::parseParameter() if (parameter.alignment != Parameter::Alignment::None) throw Error(Error::Type::ParserError, "String literals cannot be aligned or padded."); - parameter.abiType = ABIType{ABIType::String, ABIType::AlignLeft, 32}; string parsed = parseString(); + parameter.abiType = {ABIType::String, ABIType::AlignLeft, parsed.size()}; parameter.rawString += "\"" + parsed + "\""; parameter.rawBytes = BytesUtils().applyAlign( Parameter::Alignment::Left, @@ -589,8 +589,70 @@ string TestFileParser::Scanner::scanString() while (current() != '\"') { - str += current(); - advance(); + if (current() == '\\') + { + advance(); + switch (current()) + { + case '\\': + str += current(); + advance(); + break; + case 'n': + str += '\n'; + advance(); + break; + case 'r': + str += '\r'; + advance(); + break; + case 't': + str += '\t'; + advance(); + break; + case '0': + str += '\0'; + advance(); + break; + case 'x': + str += scanHexPart(); + break; + default: + throw Error(Error::Type::ParserError, "Invalid or escape sequence found in string literal."); + } + } + else + { + str += current(); + advance(); + } } return str; } + +char TestFileParser::Scanner::scanHexPart() +{ + advance(); // skip 'x' + + char value{}; + if (isdigit(current())) + value = current() - '0'; + else if (tolower(current()) >= 'a' && tolower(current()) <= 'f') + value = tolower(current()) - 'a' + 10; + else + throw Error(Error::Type::ParserError, "\\x used with no following hex digits."); + + advance(); + if (current() == '"') + return value; + + value <<= 4; + if (isdigit(current())) + value |= current() - '0'; + else if (tolower(current()) >= 'a' && tolower(current()) <= 'f') + value |= tolower(current()) - 'a' + 10; + + advance(); + + return value; +} diff --git a/test/libsolidity/util/TestFileParser.h b/test/libsolidity/util/TestFileParser.h index 9ff513a6b..dc3775357 100644 --- a/test/libsolidity/util/TestFileParser.h +++ b/test/libsolidity/util/TestFileParser.h @@ -89,15 +89,16 @@ private: std::string scanDecimalNumber(); std::string scanHexNumber(); std::string scanString(); + char scanHexPart(); private: using TokenDesc = std::pair; /// Advances current position in the input stream. - void advance() + void advance(unsigned n = 1) { solAssert(m_char != m_line.end(), "Cannot advance beyond end."); - ++m_char; + m_char = std::next(m_char, n); } /// Returns the current character or '\0' if at end of input. diff --git a/test/libsolidity/util/TestFileParserTests.cpp b/test/libsolidity/util/TestFileParserTests.cpp index 1ab147b57..92900cb32 100644 --- a/test/libsolidity/util/TestFileParserTests.cpp +++ b/test/libsolidity/util/TestFileParserTests.cpp @@ -310,6 +310,50 @@ BOOST_AUTO_TEST_CASE(call_arguments_bool) ); } +BOOST_AUTO_TEST_CASE(scanner_hex_values) +{ + char const* source = R"( + // f(uint256): "\x20\x00\xFf" -> + )"; + auto const calls = parse(source); + BOOST_REQUIRE_EQUAL(calls.size(), 1); + testFunctionCall(calls.at(0), Mode::SingleLine, "f(uint256)", false, fmt::encodeArgs(string("\x20\x00\xff", 3))); +} + +BOOST_AUTO_TEST_CASE(scanner_hex_values_invalid1) +{ + char const* source = R"( + // f(uint256): "\x" -> + )"; + BOOST_REQUIRE_THROW(parse(source), langutil::Error); +} + +BOOST_AUTO_TEST_CASE(scanner_hex_values_invalid2) +{ + char const* source = R"( + // f(uint256): "\x1" -> + )"; + auto const calls = parse(source); + BOOST_REQUIRE_EQUAL(calls.size(), 1); + testFunctionCall(calls.at(0), Mode::SingleLine, "f(uint256)", false, fmt::encodeArgs(string("\x1", 1))); +} + +BOOST_AUTO_TEST_CASE(scanner_hex_values_invalid3) +{ + char const* source = R"( + // f(uint256): "\xZ" -> + )"; + BOOST_REQUIRE_THROW(parse(source), langutil::Error); +} + +BOOST_AUTO_TEST_CASE(scanner_hex_values_invalid4) +{ + char const* source = R"( + // f(uint256): "\xZZ" -> + )"; + BOOST_REQUIRE_THROW(parse(source), langutil::Error); +} + BOOST_AUTO_TEST_CASE(call_arguments_hex_string) { char const* source = R"( diff --git a/test/libsolidity/util/TestFunctionCall.cpp b/test/libsolidity/util/TestFunctionCall.cpp index 4b9e39c19..eb9bb98e2 100644 --- a/test/libsolidity/util/TestFunctionCall.cpp +++ b/test/libsolidity/util/TestFunctionCall.cpp @@ -261,7 +261,7 @@ string TestFunctionCall::formatBytesRange( os << BytesUtils().formatHexString(_bytes); break; case ABIType::String: - os << BytesUtils().formatString(_bytes); + os << BytesUtils().formatString(_bytes, _abiType.size); break; case ABIType::Failure: break;