Merge pull request #7080 from ethereum/isoltest-string-parse-print-fix

[isoltest] Fixes parsing and printing strings with *basic* escape sequences in it.
This commit is contained in:
Christian Parpart 2019-07-22 16:28:40 +02:00 committed by GitHub
commit a640ca6fe7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 136 additions and 17 deletions

View File

@ -22,6 +22,7 @@
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <fstream> #include <fstream>
#include <iomanip>
#include <memory> #include <memory>
#include <regex> #include <regex>
#include <stdexcept> #include <stdexcept>
@ -141,22 +142,28 @@ string BytesUtils::formatHexString(bytes const& _bytes) const
return os.str(); return os.str();
} }
string BytesUtils::formatString(bytes const& _bytes) const string BytesUtils::formatString(bytes const& _bytes, size_t _cutOff) const
{ {
stringstream os; stringstream os;
os << "\""; os << "\"";
bool expectZeros = false; for (size_t i = 0; i < min(_cutOff, _bytes.size()); ++i)
for (auto const& v: _bytes)
{ {
if (expectZeros && v != 0) auto const v = _bytes[i];
return {}; switch (v)
if (v == 0) expectZeros = true;
else
{ {
if (!isprint(v) || v == '"') case '\0':
return {}; os << "\\0";
os << v; break;
case '\n':
os << "\\n";
break;
default:
if (isprint(v))
os << v;
else
os << "\\x" << setw(2) << setfill('0') << hex << v;
} }
} }
os << "\""; os << "\"";

View File

@ -76,7 +76,12 @@ public:
/// Converts \param _bytes to a soltest-compliant and human-readable /// Converts \param _bytes to a soltest-compliant and human-readable
/// string representation of a byte array which is assumed to hold /// string representation of a byte array which is assumed to hold
/// a string value. /// 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 /// Left-aligns and pads given _bytes and returns a new
/// bytes array. /// bytes array.

View File

@ -300,8 +300,8 @@ Parameter TestFileParser::parseParameter()
if (parameter.alignment != Parameter::Alignment::None) if (parameter.alignment != Parameter::Alignment::None)
throw Error(Error::Type::ParserError, "String literals cannot be aligned or padded."); throw Error(Error::Type::ParserError, "String literals cannot be aligned or padded.");
parameter.abiType = ABIType{ABIType::String, ABIType::AlignLeft, 32};
string parsed = parseString(); string parsed = parseString();
parameter.abiType = {ABIType::String, ABIType::AlignLeft, parsed.size()};
parameter.rawString += "\"" + parsed + "\""; parameter.rawString += "\"" + parsed + "\"";
parameter.rawBytes = BytesUtils().applyAlign( parameter.rawBytes = BytesUtils().applyAlign(
Parameter::Alignment::Left, Parameter::Alignment::Left,
@ -589,8 +589,70 @@ string TestFileParser::Scanner::scanString()
while (current() != '\"') while (current() != '\"')
{ {
str += current(); if (current() == '\\')
advance(); {
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; 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;
}

View File

@ -89,15 +89,16 @@ private:
std::string scanDecimalNumber(); std::string scanDecimalNumber();
std::string scanHexNumber(); std::string scanHexNumber();
std::string scanString(); std::string scanString();
char scanHexPart();
private: private:
using TokenDesc = std::pair<Token, std::string>; using TokenDesc = std::pair<Token, std::string>;
/// Advances current position in the input stream. /// Advances current position in the input stream.
void advance() void advance(unsigned n = 1)
{ {
solAssert(m_char != m_line.end(), "Cannot advance beyond end."); 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. /// Returns the current character or '\0' if at end of input.

View File

@ -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) BOOST_AUTO_TEST_CASE(call_arguments_hex_string)
{ {
char const* source = R"( char const* source = R"(

View File

@ -261,7 +261,7 @@ string TestFunctionCall::formatBytesRange(
os << BytesUtils().formatHexString(_bytes); os << BytesUtils().formatHexString(_bytes);
break; break;
case ABIType::String: case ABIType::String:
os << BytesUtils().formatString(_bytes); os << BytesUtils().formatString(_bytes, _abiType.size);
break; break;
case ABIType::Failure: case ABIType::Failure:
break; break;