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 <fstream>
#include <iomanip>
#include <memory>
#include <regex>
#include <stdexcept>
@ -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 << "\"";

View File

@ -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.

View File

@ -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;
}

View File

@ -89,15 +89,16 @@ private:
std::string scanDecimalNumber();
std::string scanHexNumber();
std::string scanString();
char scanHexPart();
private:
using TokenDesc = std::pair<Token, std::string>;
/// 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.

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

View File

@ -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;