Skip to content

Disallow non-default expectedExecutionsPerDeployment in evmasm tests when only a creation object is present #16023

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions test/libevmasm/EVMAssemblyTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,13 @@ EVMAssemblyTest::EVMAssemblyTest(std::string const& _filename):
"none"
);
m_optimizerSettings = Assembly::OptimiserSettings::translateSettings(OptimiserSettings::preset(optimizationPreset));
size_t defaultExpectedExecutionsPerDeployment = m_optimizerSettings.expectedExecutionsPerDeployment;
m_optimizerSettings.expectedExecutionsPerDeployment = m_reader.sizetSetting(
"optimizer.expectedExecutionsPerDeployment",
m_optimizerSettings.expectedExecutionsPerDeployment
);
m_usingDefaultExpectedExecutionsPerDeployment =
m_optimizerSettings.expectedExecutionsPerDeployment == defaultExpectedExecutionsPerDeployment;

auto const optimizerComponentSetting = [&](std::string const& _component, bool& _setting) {
_setting = m_reader.boolSetting("optimizer." + _component, _setting);
Expand Down Expand Up @@ -132,6 +135,14 @@ TestCase::TestResult EVMAssemblyTest::run(std::ostream& _stream, std::string con
return checkResult(_stream, _linePrefix, _formatted);
}

soltestAssert(evmAssemblyStack.evmAssembly());
if (!m_usingDefaultExpectedExecutionsPerDeployment && evmAssemblyStack.evmAssembly()->numSubs() == 0)
// This is a common mistake. We can't issue a warning here, so let's report it as an error.
BOOST_THROW_EXCEPTION(std::runtime_error(
"The custom value specified for optimizer.expectedExecutionsPerDeployment has no effect "
"on the creation assembly, which is the only assembly in the test."
));

try
{
evmAssemblyStack.assemble();
Expand Down
1 change: 1 addition & 0 deletions test/libevmasm/EVMAssemblyTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class EVMAssemblyTest: public frontend::test::EVMVersionRestrictedTestCase
AssemblyFormat m_assemblyFormat{};
std::string m_selectedOutputs;
evmasm::Assembly::OptimiserSettings m_optimizerSettings;
bool m_usingDefaultExpectedExecutionsPerDeployment{};
};

}
102 changes: 90 additions & 12 deletions test/libevmasm/PlainAssemblyParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@

#include <fmt/format.h>

#include <sstream>

using namespace std::string_literals;
using namespace solidity;
using namespace solidity::test;
Expand All @@ -39,16 +37,46 @@ using namespace solidity::langutil;

Json PlainAssemblyParser::parse(std::string _sourceName, std::string const& _source)
{
m_sourceStream = std::istringstream(_source);
m_sourceName = std::move(_sourceName);
Json codeJSON = Json::array();
std::istringstream sourceStream(_source);
while (getline(sourceStream, m_line))
m_lineNumber = 0;

advanceLine();
return parseAssembly(0);
}

Json PlainAssemblyParser::parseAssembly(size_t _nestingLevel)
{
Json assemblyJSON = {{".code", Json::array()}};
Json& codeJSON = assemblyJSON[".code"];

while (m_line.has_value())
{
advanceLine(m_line);
if (m_lineTokens.empty())
{
advanceLine();
continue;
}

size_t newLevel = parseNestingLevel();
if (newLevel > _nestingLevel)
BOOST_THROW_EXCEPTION(std::runtime_error(formatError("Indentation does not match the current subassembly nesting level.")));

if (newLevel < _nestingLevel)
return assemblyJSON;

if (currentToken().value == ".sub")
{
advanceLine();

std::string nextDataIndex = std::to_string(assemblyJSON[".data"].size());
assemblyJSON[".data"][nextDataIndex] = parseAssembly(_nestingLevel + 1);
continue;
}
else if (assemblyJSON.contains(".data"))
BOOST_THROW_EXCEPTION(std::runtime_error(formatError("The code of an assembly must be specified before its subassemblies.")));

if (c_instructions.contains(currentToken().value))
if (c_instructions.contains(currentToken().value) || currentToken().value == "PUSHSIZE")
{
expectNoMoreArguments();
codeJSON.push_back({{"name", currentToken().value}});
Expand All @@ -62,6 +90,19 @@ Json PlainAssemblyParser::parse(std::string _sourceName, std::string const& _sou
expectNoMoreArguments();
codeJSON.push_back({{"name", "PUSH [tag]"}, {"value", tagID}});
}
else if (hasMoreTokens() && (nextToken().value == "[$]" || nextToken().value == "#[$]"))
{
std::string pushType = std::string(nextToken().value);
advanceToken();
std::string_view subassemblyID = expectArgument();
expectNoMoreArguments();

if (!subassemblyID.starts_with("0x"))
BOOST_THROW_EXCEPTION(std::runtime_error(formatError("The subassembly ID must be a hex number prefixed with '0x'.")));

subassemblyID.remove_prefix("0x"s.size());
codeJSON.push_back({{"name", "PUSH " + pushType}, {"value", subassemblyID}});
}
else
{
std::string_view immediateArgument = expectArgument();
Expand All @@ -84,8 +125,24 @@ Json PlainAssemblyParser::parse(std::string _sourceName, std::string const& _sou
}
else
BOOST_THROW_EXCEPTION(std::runtime_error(formatError("Unknown instruction.")));

advanceLine();
}
return {{".code", codeJSON}};

return assemblyJSON;
}

size_t PlainAssemblyParser::parseNestingLevel() const
{
std::string_view indentationString = indentation();

if (indentationString != std::string(indentationString.size(), ' '))
BOOST_THROW_EXCEPTION(std::runtime_error(formatError("Non-space characters used for indentation.")));

if (indentationString.size() % 4 != 0)
BOOST_THROW_EXCEPTION(std::runtime_error(formatError("Each indentation level must consist of 4 spaces.")));

return indentationString.size() / 4;
}

PlainAssemblyParser::Token const& PlainAssemblyParser::currentToken() const
Expand All @@ -100,6 +157,16 @@ PlainAssemblyParser::Token const& PlainAssemblyParser::nextToken() const
return m_lineTokens[m_tokenIndex + 1];
}

std::string_view PlainAssemblyParser::indentation() const
{
soltestAssert(m_line.has_value());

if (m_lineTokens.empty())
return *m_line;

return std::string_view(*m_line).substr(0, m_lineTokens.at(0).position);
}

bool PlainAssemblyParser::advanceToken()
{
if (!hasMoreTokens())
Expand All @@ -125,12 +192,20 @@ void PlainAssemblyParser::expectNoMoreArguments()
BOOST_THROW_EXCEPTION(std::runtime_error(formatError("Too many arguments.")));
}

void PlainAssemblyParser::advanceLine(std::string_view _line)
bool PlainAssemblyParser::advanceLine()
{
std::string line;
if (!getline(m_sourceStream, line))
{
m_line = std::nullopt;
return false;
}

++m_lineNumber;
m_line = _line;
m_lineTokens = tokenizeLine(m_line);
m_line = std::move(line);
m_lineTokens = tokenizeLine(*m_line);
m_tokenIndex = 0;
return true;
}

std::vector<PlainAssemblyParser::Token> PlainAssemblyParser::tokenizeLine(std::string_view _line)
Expand Down Expand Up @@ -162,6 +237,9 @@ std::vector<PlainAssemblyParser::Token> PlainAssemblyParser::tokenizeLine(std::s

std::string PlainAssemblyParser::formatError(std::string_view _message) const
{
soltestAssert(m_line.has_value());
soltestAssert(!m_lineTokens.empty());

std::string lineNumberString = std::to_string(m_lineNumber);
std::string padding(lineNumberString.size(), ' ');
std::string underline = std::string(currentToken().position, ' ') + std::string(currentToken().value.size(), '^');
Expand All @@ -174,7 +252,7 @@ std::string PlainAssemblyParser::formatError(std::string_view _message) const
_message,
padding, m_sourceName,
padding,
m_lineNumber, m_line,
m_lineNumber, *m_line,
padding, underline
);
}
27 changes: 21 additions & 6 deletions test/libevmasm/PlainAssemblyParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include <libsolutil/JSON.h>

#include <sstream>
#include <string>
#include <string_view>
#include <vector>
Expand All @@ -36,12 +37,20 @@ namespace solidity::evmasm::test
/// - A non-empty line represents a single assembly item.
/// - The name of the item is the first thing on the line and may consist of one or more tokens.
/// - One or more arguments follow the name.
/// - Indentation determines assembly nesting level (4 spaces per level).
/// - A new subassembly starts with '.sub' and contains all subsequent lines at a higher nesting level.
/// The first line at the same or lower nesting level ends the subassembly.
/// - Subassemblies can be nested to arbitrary depth.
/// - The code of an assembly must be specified before its subassemblies.
///
/// Supported items:
/// - All instruction names.
/// - PUSH <hex value>
/// - PUSH [tag] <tagID>
/// - tag <tagID>
/// - PUSH [$] <subassemblyID>
/// - PUSH #[$] <subassemblyID>
/// - .sub
class PlainAssemblyParser
{
public:
Expand All @@ -56,24 +65,30 @@ class PlainAssemblyParser
size_t position; ///< Position of the first character of the token within m_line.
};

Json parseAssembly(size_t _nestingLevel);
size_t parseNestingLevel() const;

Token const& currentToken() const;
Token const& nextToken() const;
bool hasMoreTokens() const { return m_tokenIndex + 1 < m_lineTokens.size(); }

std::string_view indentation() const;

bool advanceToken();
std::string_view expectArgument();
void expectNoMoreArguments();
void advanceLine(std::string_view _line);
bool advanceLine();

static std::vector<Token> tokenizeLine(std::string_view _line);
std::string formatError(std::string_view _message) const;

private:
std::string m_sourceName; ///< Name of the file the source comes from.
size_t m_lineNumber = 0; ///< The number of the current line within the source, 1-based.
std::string m_line; ///< The current line, unparsed.
std::vector<Token> m_lineTokens; ///< Decomposition of the current line into tokens (does not include comments).
size_t m_tokenIndex = 0; ///< Points at a token within m_lineTokens.
std::istringstream m_sourceStream; ///< The source code being parsed.
std::string m_sourceName; ///< Name of the file the source comes from.
size_t m_lineNumber = 0; ///< The number of the current line within the source, 1-based.
std::optional<std::string> m_line; ///< The current line, unparsed.
std::vector<Token> m_lineTokens; ///< Decomposition of the current line into tokens (does not include comments).
size_t m_tokenIndex = 0; ///< Points at a token within m_lineTokens.
};

}
10 changes: 10 additions & 0 deletions test/libevmasm/evmAssemblyTests/isoltestTesting/empty.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// ====
// outputs: InputAssemblyJSON,Assembly,Bytecode,Opcodes,SourceMappings
// ----
// InputAssemblyJSON: {
// ".code": []
// }
// Assembly:
// Bytecode:
// Opcodes:
// SourceMappings:
33 changes: 33 additions & 0 deletions test/libevmasm/evmAssemblyTests/isoltestTesting/pushsize.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
PUSHSIZE

.sub
PUSHSIZE
// ====
// outputs: InputAssemblyJSON,Assembly,Bytecode,Opcodes,SourceMappings
// ----
// InputAssemblyJSON: {
// ".code": [
// {
// "name": "PUSHSIZE"
// }
// ],
// ".data": {
// "0": {
// ".code": [
// {
// "name": "PUSHSIZE"
// }
// ]
// }
// }
// }
// Assembly:
// bytecodeSize
// stop
//
// sub_0: assembly {
// bytecodeSize
// }
// Bytecode: 6003fe
// Opcodes: PUSH1 0x3 INVALID
// SourceMappings: :::-:0
72 changes: 72 additions & 0 deletions test/libevmasm/evmAssemblyTests/isoltestTesting/pushsubsize.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
PUSH [$] 0x0000
PUSH #[$] 0x0000

.sub
PUSH [$] 0x0
PUSH #[$] 0x2

.sub
.sub
.sub
// ====
// outputs: InputAssemblyJSON,Assembly,Bytecode,Opcodes,SourceMappings
// ----
// InputAssemblyJSON: {
// ".code": [
// {
// "name": "PUSH [$]",
// "value": "0000"
// },
// {
// "name": "PUSH #[$]",
// "value": "0000"
// }
// ],
// ".data": {
// "0": {
// ".code": [
// {
// "name": "PUSH [$]",
// "value": "0"
// },
// {
// "name": "PUSH #[$]",
// "value": "2"
// }
// ],
// ".data": {
// "0": {
// ".code": []
// },
// "1": {
// ".code": []
// },
// "2": {
// ".code": []
// }
// }
// }
// }
// }
// Assembly:
// dataOffset(sub_0)
// dataSize(sub_0)
// stop
//
// sub_0: assembly {
// dataOffset(sub_0)
// dataSize(sub_2)
// stop
//
// sub_0: assembly {
// }
//
// sub_1: assembly {
// }
//
// sub_2: assembly {
// }
// }
// Bytecode: 60056005fe60056000fe
// Opcodes: PUSH1 0x5 PUSH1 0x5 INVALID PUSH1 0x5 PUSH1 0x0 INVALID
// SourceMappings: :::-:0;
Loading