Merge branch 'develop' into fixNoMobile

This commit is contained in:
chriseth 2017-02-24 10:39:55 +01:00 committed by GitHub
commit 92bf5154fd
21 changed files with 485 additions and 171 deletions

View File

@ -9,8 +9,11 @@ Features:
Bugfixes:
* Commandline interface: Always escape filenames (replace ``/``, ``:`` and ``.`` with ``_``).
* Commandline interface: Do not try creating paths ``.`` and ``..``.
* Type system: Fix a crash caused by continuing on fatal errors in the code.
* Type system: Disallow arrays with negative length.
* Type system: Fix a crash related to invalid binary operators.
* Inline assembly: Charge one stack slot for non-value types during analysis.
* Assembly output: Print source location before the operation it refers to instead of after.
### 0.4.9 (2017-01-31)

View File

@ -81,7 +81,7 @@ This is as opposed to the more intuitive sending pattern:
mostSent = msg.value;
}
function becomeRichest() returns (bool) {
function becomeRichest() payable returns (bool) {
if (msg.value > mostSent) {
// Check if call succeeds to prevent an attacker
// from trapping the previous person's funds in

View File

@ -102,6 +102,22 @@ We will re-add the pre-built bottles soon.
brew install solidity
brew linkapps solidity
If you need a specific version of Solidity you can install a
Homebrew formula directly from Github.
View
`solidity.rb commits on Github <https://github.com/ethereum/homebrew-ethereum/commits/master/solidity.rb>`_.
Follow the history links until you have a raw file link of a
specific commit of ``solidity.rb``.
Install it using ``brew``:
.. code:: bash
brew unlink solidity
# Install 0.4.8
brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb
.. _building-from-source:

View File

@ -130,8 +130,8 @@ public:
if (!_item.location().isEmpty() && _item.location() != m_location)
{
flush();
printLocation();
m_location = _item.location();
printLocation();
}
if (!(
_item.canBeFunctional() &&

View File

@ -21,6 +21,7 @@
*/
#include <libsolidity/analysis/NameAndTypeResolver.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/analysis/TypeChecker.h>
#include <libsolidity/interface/Exceptions.h>
@ -130,62 +131,9 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsideCode)
{
bool success = true;
try
{
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(&_node))
{
m_currentScope = m_scopes[contract->scope()].get();
solAssert(!!m_currentScope, "");
for (ASTPointer<InheritanceSpecifier> const& baseContract: contract->baseContracts())
if (!resolveNamesAndTypes(*baseContract, true))
success = false;
m_currentScope = m_scopes[contract].get();
if (success)
{
linearizeBaseContracts(*contract);
vector<ContractDefinition const*> properBases(
++contract->annotation().linearizedBaseContracts.begin(),
contract->annotation().linearizedBaseContracts.end()
);
for (ContractDefinition const* base: properBases)
importInheritedScope(*base);
}
// these can contain code, only resolve parameters for now
for (ASTPointer<ASTNode> const& node: contract->subNodes())
{
m_currentScope = m_scopes[contract].get();
if (!resolveNamesAndTypes(*node, false))
success = false;
}
if (!success)
return false;
if (!_resolveInsideCode)
return success;
m_currentScope = m_scopes[contract].get();
// now resolve references inside the code
for (ASTPointer<ASTNode> const& node: contract->subNodes())
{
m_currentScope = m_scopes[contract].get();
if (!resolveNamesAndTypes(*node, true))
success = false;
}
}
else
{
if (m_scopes.count(&_node))
m_currentScope = m_scopes[&_node].get();
return ReferencesResolver(m_errors, *this, _resolveInsideCode).resolve(_node);
}
return resolveNamesAndTypesInternal(_node, _resolveInsideCode);
}
catch (FatalError const&)
{
@ -193,7 +141,6 @@ bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsi
throw; // Something is weird here, rather throw again.
return false;
}
return success;
}
bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
@ -249,21 +196,25 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
solAssert(_declarations.size() > 1, "");
vector<Declaration const*> uniqueFunctions;
for (auto it = _declarations.begin(); it != _declarations.end(); ++it)
for (Declaration const* declaration: _declarations)
{
solAssert(*it, "");
solAssert(declaration, "");
// the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1
solAssert(dynamic_cast<FunctionDefinition const*>(*it) || dynamic_cast<EventDefinition const*>(*it) || dynamic_cast<VariableDeclaration const*>(*it),
"Found overloading involving something not a function or a variable");
solAssert(
dynamic_cast<FunctionDefinition const*>(declaration) ||
dynamic_cast<EventDefinition const*>(declaration) ||
dynamic_cast<VariableDeclaration const*>(declaration),
"Found overloading involving something not a function or a variable."
);
shared_ptr<FunctionType const> functionType { (*it)->functionType(false) };
FunctionTypePointer functionType { declaration->functionType(false) };
if (!functionType)
functionType = (*it)->functionType(true);
solAssert(functionType, "failed to determine the function type of the overloaded");
functionType = declaration->functionType(true);
solAssert(functionType, "Failed to determine the function type of the overloaded.");
for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes())
if (!parameter)
reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context");
reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context.");
if (uniqueFunctions.end() == find_if(
uniqueFunctions.begin(),
@ -276,11 +227,73 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
return newFunctionType && functionType->hasEqualArgumentTypes(*newFunctionType);
}
))
uniqueFunctions.push_back(*it);
uniqueFunctions.push_back(declaration);
}
return uniqueFunctions;
}
bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode)
{
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(&_node))
{
bool success = true;
m_currentScope = m_scopes[contract->scope()].get();
solAssert(!!m_currentScope, "");
for (ASTPointer<InheritanceSpecifier> const& baseContract: contract->baseContracts())
if (!resolveNamesAndTypes(*baseContract, true))
success = false;
m_currentScope = m_scopes[contract].get();
if (success)
{
linearizeBaseContracts(*contract);
vector<ContractDefinition const*> properBases(
++contract->annotation().linearizedBaseContracts.begin(),
contract->annotation().linearizedBaseContracts.end()
);
for (ContractDefinition const* base: properBases)
importInheritedScope(*base);
}
// these can contain code, only resolve parameters for now
for (ASTPointer<ASTNode> const& node: contract->subNodes())
{
m_currentScope = m_scopes[contract].get();
if (!resolveNamesAndTypes(*node, false))
{
success = false;
break;
}
}
if (!success)
return false;
if (!_resolveInsideCode)
return success;
m_currentScope = m_scopes[contract].get();
// now resolve references inside the code
for (ASTPointer<ASTNode> const& node: contract->subNodes())
{
m_currentScope = m_scopes[contract].get();
if (!resolveNamesAndTypes(*node, true))
success = false;
}
return success;
}
else
{
if (m_scopes.count(&_node))
m_currentScope = m_scopes[&_node].get();
return ReferencesResolver(m_errors, *this, _resolveInsideCode).resolve(_node);
}
}
void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base)
{
auto iterator = m_scopes.find(&_base);

View File

@ -89,6 +89,9 @@ public:
);
private:
/// Internal version of @a resolveNamesAndTypes (called from there) throws exceptions on fatal errors.
bool resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode = true);
/// Imports all members declared directly in the given contract (i.e. does not import inherited members)
/// into the current scope if they are not present already.
void importInheritedScope(ContractDefinition const& _base);

View File

@ -34,15 +34,8 @@ using namespace dev::solidity;
bool ReferencesResolver::resolve(ASTNode const& _root)
{
try
{
_root.accept(*this);
}
catch (FatalError const&)
{
solAssert(m_errorOccurred, "");
}
return !m_errorOccurred;
}

View File

@ -52,7 +52,7 @@ public:
m_resolveInsideCode(_resolveInsideCode)
{}
/// @returns true if no errors during resolving
/// @returns true if no errors during resolving and throws exceptions on fatal errors.
bool resolve(ASTNode const& _root);
private:

View File

@ -611,7 +611,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly.");
if (var->isLocalVariable())
pushes = var->type()->sizeOnStack();
else if (var->type()->isValueType())
else if (!var->type()->isValueType())
pushes = 1;
else
pushes = 2; // slot number, intra slot offset

View File

@ -1652,6 +1652,7 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const
for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
{
TypePointer type = variable->annotation().type;
solAssert(type, "");
// Skip all mapping members if we are not in storage.
if (location() != DataLocation::Storage && !type->canLiveOutsideStorage())
continue;
@ -1967,6 +1968,8 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
if (auto structType = dynamic_cast<StructType const*>(returnType.get()))
{
for (auto const& member: structType->members(nullptr))
{
solAssert(member.type, "");
if (member.type->category() != Category::Mapping)
{
if (auto arrayType = dynamic_cast<ArrayType const*>(member.type.get()))
@ -1976,6 +1979,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
retParamNames.push_back(member.name);
}
}
}
else
{
retParams.push_back(ReferenceType::copyForLocationIfReference(

View File

@ -190,6 +190,10 @@ public:
}
(*this)(_instr.instruction);
}
void operator()(assembly::FunctionCall const&)
{
solAssert(false, "Function call not removed during desugaring phase.");
}
void operator()(Label const& _label)
{
m_state.assembly.setSourceLocation(_label.location);
@ -249,7 +253,10 @@ public:
_block.location
);
}
}
void operator()(assembly::FunctionDefinition const&)
{
solAssert(false, "Function definition not removed during desugaring phase.");
}
private:

View File

@ -48,17 +48,22 @@ struct Label { SourceLocation location; std::string name; };
struct Assignment { SourceLocation location; Identifier variableName; };
struct FunctionalAssignment;
struct VariableDeclaration;
struct FunctionDefinition;
struct FunctionCall;
struct Block;
using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionalInstruction, VariableDeclaration, Block>;
using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Block>;
/// Functional assignment ("x := mload(20)", expects push-1-expression on the right hand
/// side and requires x to occupy exactly one stack slot.
struct FunctionalAssignment { SourceLocation location; Identifier variableName; std::shared_ptr<Statement> value; };
/// Functional instruction, e.g. "mul(mload(20), add(2, x))"
struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; };
struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Statement> arguments; };
/// Block-scope variable declaration ("let x := mload(20)"), non-hoisted
struct VariableDeclaration { SourceLocation location; std::string name; std::shared_ptr<Statement> value; };
/// Block that creates a scope (frees declared stack variables)
struct Block { SourceLocation location; std::vector<Statement> statements; };
/// Function definition ("function f(a, b) -> (d, e) { ... }")
struct FunctionDefinition { SourceLocation location; std::string name; std::vector<std::string> arguments; std::vector<std::string> returns; Block body; };
struct LocationExtractor: boost::static_visitor<SourceLocation>
{

View File

@ -62,6 +62,8 @@ assembly::Statement Parser::parseStatement()
{
case Token::Let:
return parseVariableDeclaration();
case Token::Function:
return parseFunctionDefinition();
case Token::LBrace:
return parseBlock();
case Token::Assign:
@ -214,10 +216,7 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration()
{
VariableDeclaration varDecl = createWithLocation<VariableDeclaration>();
expectToken(Token::Let);
varDecl.name = m_scanner->currentLiteral();
if (instructions().count(varDecl.name))
fatalParserError("Cannot use instruction names for identifier names.");
expectToken(Token::Identifier);
varDecl.name = expectAsmIdentifier();
expectToken(Token::Colon);
expectToken(Token::Assign);
varDecl.value.reset(new Statement(parseExpression()));
@ -225,10 +224,43 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration()
return varDecl;
}
FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement&& _instruction)
assembly::FunctionDefinition Parser::parseFunctionDefinition()
{
FunctionDefinition funDef = createWithLocation<FunctionDefinition>();
expectToken(Token::Function);
funDef.name = expectAsmIdentifier();
expectToken(Token::LParen);
while (m_scanner->currentToken() != Token::RParen)
{
funDef.arguments.push_back(expectAsmIdentifier());
if (m_scanner->currentToken() == Token::RParen)
break;
expectToken(Token::Comma);
}
expectToken(Token::RParen);
if (m_scanner->currentToken() == Token::Sub)
{
expectToken(Token::Sub);
expectToken(Token::GreaterThan);
expectToken(Token::LParen);
while (true)
{
funDef.returns.push_back(expectAsmIdentifier());
if (m_scanner->currentToken() == Token::RParen)
break;
expectToken(Token::Comma);
}
expectToken(Token::RParen);
}
funDef.body = parseBlock();
funDef.location.end = funDef.body.location.end;
return funDef;
}
assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _instruction)
{
if (_instruction.type() == typeid(Instruction))
{
if (_instruction.type() != typeid(Instruction))
fatalParserError("Assembly instruction required in front of \"(\")");
FunctionalInstruction ret;
ret.instruction = std::move(boost::get<Instruction>(_instruction));
ret.location = ret.instruction.location;
@ -238,7 +270,6 @@ FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement&& _
fatalParserError("DUPi instructions not allowed for functional notation");
if (solidity::Instruction::SWAP1 <= instr && instr <= solidity::Instruction::SWAP16)
fatalParserError("SWAPi instructions not allowed for functional notation");
expectToken(Token::LParen);
unsigned args = unsigned(instrInfo.args);
for (unsigned i = 0; i < args; ++i)
@ -266,3 +297,34 @@ FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement&& _
expectToken(Token::RParen);
return ret;
}
else if (_instruction.type() == typeid(Identifier))
{
FunctionCall ret;
ret.functionName = std::move(boost::get<Identifier>(_instruction));
ret.location = ret.functionName.location;
expectToken(Token::LParen);
while (m_scanner->currentToken() != Token::RParen)
{
ret.arguments.emplace_back(parseExpression());
if (m_scanner->currentToken() == Token::RParen)
break;
expectToken(Token::Comma);
}
ret.location.end = endPosition();
expectToken(Token::RParen);
return ret;
}
else
fatalParserError("Assembly instruction or function name required in front of \"(\")");
return {};
}
string Parser::expectAsmIdentifier()
{
string name = m_scanner->currentLiteral();
if (instructions().count(name))
fatalParserError("Cannot use instruction names for identifier names.");
expectToken(Token::Identifier);
return name;
}

View File

@ -67,7 +67,9 @@ protected:
std::map<std::string, dev::solidity::Instruction> const& instructions();
Statement parseElementaryOperation(bool _onlySinglePusher = false);
VariableDeclaration parseVariableDeclaration();
FunctionalInstruction parseFunctionalInstruction(Statement&& _instruction);
FunctionDefinition parseFunctionDefinition();
Statement parseFunctionalInstruction(Statement&& _instruction);
std::string expectAsmIdentifier();
};
}

View File

@ -112,6 +112,24 @@ string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDecl
return "let " + _variableDeclaration.name + " := " + boost::apply_visitor(*this, *_variableDeclaration.value);
}
string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefinition)
{
string out = "function " + _functionDefinition.name + "(" + boost::algorithm::join(_functionDefinition.arguments, ", ") + ")";
if (!_functionDefinition.returns.empty())
out += " -> (" + boost::algorithm::join(_functionDefinition.returns, ", ") + ")";
return out + "\n" + (*this)(_functionDefinition.body);
}
string AsmPrinter::operator()(assembly::FunctionCall const& _functionCall)
{
return
(*this)(_functionCall.functionName) + "(" +
boost::algorithm::join(
_functionCall.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)),
", " ) +
")";
}
string AsmPrinter::operator()(Block const& _block)
{
if (_block.statements.empty())

View File

@ -53,6 +53,8 @@ public:
std::string operator()(assembly::Assignment const& _assignment);
std::string operator()(assembly::FunctionalAssignment const& _functionalAssignment);
std::string operator()(assembly::VariableDeclaration const& _variableDeclaration);
std::string operator()(assembly::FunctionDefinition const& _functionDefinition);
std::string operator()(assembly::FunctionCall const& _functionCall);
std::string operator()(assembly::Block const& _block);
};

View File

@ -7,23 +7,9 @@ aux_source_directory(libsolidity SRC_LIST)
aux_source_directory(contracts SRC_LIST)
aux_source_directory(liblll SRC_LIST)
get_filename_component(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE)
list(REMOVE_ITEM SRC_LIST "./fuzzer.cpp")
# search for test names and create ctest tests
enable_testing()
foreach(file ${SRC_LIST})
file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/${file} test_list_raw REGEX "BOOST_.*TEST_(SUITE|CASE)")
set(TestSuite "DEFAULT")
foreach(test_raw ${test_list_raw})
string(REGEX REPLACE ".*TEST_(SUITE|CASE)\\(([^ ,\\)]*).*" "\\1 \\2" test ${test_raw})
if(test MATCHES "^SUITE .*")
string(SUBSTRING ${test} 6 -1 TestSuite)
elseif(test MATCHES "^CASE .*")
string(SUBSTRING ${test} 5 -1 TestCase)
add_test(NAME ${TestSuite}/${TestCase} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test COMMAND test -t ${TestSuite}/${TestCase})
endif(test MATCHES "^SUITE .*")
endforeach(test_raw)
endforeach(file)
get_filename_component(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE)
file(GLOB HEADERS "*.h" "*/*.h")
set(EXECUTABLE soltest)
@ -34,5 +20,5 @@ eth_use(${EXECUTABLE} REQUIRED Solidity::solidity Solidity::lll)
include_directories(BEFORE ..)
target_link_libraries(${EXECUTABLE} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
enable_testing()
set(CTEST_OUTPUT_ON_FAILURE TRUE)
add_executable(solfuzzer fuzzer.cpp)
target_link_libraries(solfuzzer soljson)

View File

@ -46,6 +46,21 @@ do
test -z "$output" -a "$failed" -eq 0
done
# Test library checksum
echo 'contact C {}' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222
! echo 'contract C {}' | "$SOLC" --link --libraries a:0x80f20564390eAe531E810af625A22f51385Cd222 2>/dev/null
echo "Testing library checksum..."
echo '' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222
! echo '' | "$SOLC" --link --libraries a:0x80f20564390eAe531E810af625A22f51385Cd222 2>/dev/null
echo "Testing soljson via the fuzzer..."
TMPDIR=$(mktemp -d)
(
cd "$REPO_ROOT"
REPO_ROOT=$(pwd) # make it absolute
cd "$TMPDIR"
"$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/test/contracts/* "$REPO_ROOT"/test/libsolidity/*EndToEnd*
for f in *.sol
do
"$REPO_ROOT"/build/test/solfuzzer < "$f"
done
)
rm -rf "$TMPDIR"
echo "Done."

92
test/fuzzer.cpp Normal file
View File

@ -0,0 +1,92 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Executable for use with AFL <http://lcamtuf.coredump.cx/afl>.
* Reads a single source from stdin and signals a failure for internal errors.
*/
#include <json/json.h>
#include <string>
#include <iostream>
using namespace std;
extern "C"
{
extern char const* compileJSON(char const* _input, bool _optimize);
}
string contains(string const& _haystack, vector<string> const& _needles)
{
for (string const& needle: _needles)
if (_haystack.find(needle) != string::npos)
return needle;
return "";
}
int main()
{
string input;
while (!cin.eof())
{
string s;
getline(cin, s);
input += s + '\n';
}
bool optimize = true;
string outputString(compileJSON(input.c_str(), optimize));
Json::Value outputJson;
if (!Json::Reader().parse(outputString, outputJson))
{
cout << "Compiler produced invalid JSON output." << endl;
abort();
}
if (outputJson.isMember("errors"))
{
if (!outputJson["errors"].isArray())
{
cout << "Output JSON has \"errors\" but it is not an array." << endl;
abort();
}
for (Json::Value const& error: outputJson["errors"])
{
string invalid = contains(error.asString(), vector<string>{
"Compiler error",
"Internal compiler error",
"Exception during compilation",
"Unknown exception during compilation",
"Unknown exception while generating contract data output",
"Unknown exception while generating formal method output",
"Unknown exception while generating source name output",
"Unknown error while generating JSON"
});
if (!invalid.empty())
{
cout << "Invalid error: \"" << invalid << "\"" << endl;
abort();
}
}
}
else if (!outputJson.isMember("contracts"))
{
cout << "Output JSON has neither \"errors\" nor \"contracts\"." << endl;
abort();
}
return 0;
}

View File

@ -20,14 +20,19 @@
* Unit tests for inline assembly.
*/
#include <string>
#include <memory>
#include <libevmasm/Assembly.h>
#include <libsolidity/parsing/Scanner.h>
#include "../TestHelper.h"
#include <libsolidity/inlineasm/AsmStack.h>
#include <libsolidity/parsing/Scanner.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/ast/AST.h>
#include "../TestHelper.h"
#include <test/libsolidity/ErrorCheck.h>
#include <libevmasm/Assembly.h>
#include <boost/optional.hpp>
#include <string>
#include <memory>
using namespace std;
@ -41,31 +46,44 @@ namespace test
namespace
{
bool successParse(std::string const& _source, bool _assemble = false, bool _allowWarnings = true)
boost::optional<Error> parseAndReturnFirstError(string const& _source, bool _assemble = false, bool _allowWarnings = true)
{
assembly::InlineAssemblyStack stack;
bool success = false;
try
{
if (!stack.parse(std::make_shared<Scanner>(CharStream(_source))))
return false;
if (_assemble)
{
success = stack.parse(std::make_shared<Scanner>(CharStream(_source)));
if (success && _assemble)
stack.assemble();
if (!stack.errors().empty())
if (!_allowWarnings || !Error::containsOnlyWarnings(stack.errors()))
return false;
}
}
catch (FatalError const&)
{
if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError))
return false;
BOOST_FAIL("Fatal error leaked.");
success = false;
}
if (!success)
{
BOOST_CHECK_EQUAL(stack.errors().size(), 1);
return *stack.errors().front();
}
else
{
// If success is true, there might still be an error in the assembly stage.
if (_allowWarnings && Error::containsOnlyWarnings(stack.errors()))
return {};
else if (!stack.errors().empty())
{
if (!_allowWarnings)
BOOST_CHECK_EQUAL(stack.errors().size(), 1);
return *stack.errors().front();
}
}
return {};
}
if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError))
return false;
BOOST_CHECK(Error::containsOnlyWarnings(stack.errors()));
return true;
bool successParse(std::string const& _source, bool _assemble = false, bool _allowWarnings = true)
{
return !parseAndReturnFirstError(_source, _assemble, _allowWarnings);
}
bool successAssemble(string const& _source, bool _allowWarnings = true)
@ -73,6 +91,14 @@ bool successAssemble(string const& _source, bool _allowWarnings = true)
return successParse(_source, true, _allowWarnings);
}
Error expectError(std::string const& _source, bool _assemble, bool _allowWarnings = false)
{
auto error = parseAndReturnFirstError(_source, _assemble, _allowWarnings);
BOOST_REQUIRE(error);
return *error;
}
void parsePrintCompare(string const& _source)
{
assembly::InlineAssemblyStack stack;
@ -83,6 +109,21 @@ void parsePrintCompare(string const& _source)
}
#define CHECK_ERROR(text, assemble, typ, substring) \
do \
{ \
Error err = expectError((text), (assemble), false); \
BOOST_CHECK(err.type() == (Error::Type::typ)); \
BOOST_CHECK(searchErrorMessage(err, (substring))); \
} while(0)
#define CHECK_PARSE_ERROR(text, type, substring) \
CHECK_ERROR(text, false, type, substring)
#define CHECK_ASSEMBLE_ERROR(text, type, substring) \
CHECK_ERROR(text, true, type, substring)
BOOST_AUTO_TEST_SUITE(SolidityInlineAssembly)
@ -159,6 +200,21 @@ BOOST_AUTO_TEST_CASE(blocks)
BOOST_CHECK(successParse("{ let x := 7 { let y := 3 } { let z := 2 } }"));
}
BOOST_AUTO_TEST_CASE(function_definitions)
{
BOOST_CHECK(successParse("{ function f() { } function g(a) -> (x) { } }"));
}
BOOST_AUTO_TEST_CASE(function_definitions_multiple_args)
{
BOOST_CHECK(successParse("{ function f(a, d) { } function g(a, d) -> (x, y) { } }"));
}
BOOST_AUTO_TEST_CASE(function_calls)
{
BOOST_CHECK(successParse("{ g(1, 2, f(mul(2, 3))) x() }"));
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(Printing)
@ -209,6 +265,16 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode)
parsePrintCompare(parsed);
}
BOOST_AUTO_TEST_CASE(function_definitions_multiple_args)
{
parsePrintCompare("{\n function f(a, d)\n {\n mstore(a, d)\n }\n function g(a, d) -> (x, y)\n {\n }\n}");
}
BOOST_AUTO_TEST_CASE(function_calls)
{
parsePrintCompare("{\n g(1, mul(2, x), f(mul(2, 3)))\n x()\n}");
}
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(Analysis)
@ -220,7 +286,7 @@ BOOST_AUTO_TEST_CASE(string_literals)
BOOST_AUTO_TEST_CASE(oversize_string_literals)
{
BOOST_CHECK(!successAssemble("{ let x := \"123456789012345678901234567890123\" }"));
CHECK_ASSEMBLE_ERROR("{ let x := \"123456789012345678901234567890123\" }", TypeError, "String literal too long");
}
BOOST_AUTO_TEST_CASE(assignment_after_tag)
@ -230,15 +296,16 @@ BOOST_AUTO_TEST_CASE(assignment_after_tag)
BOOST_AUTO_TEST_CASE(magic_variables)
{
BOOST_CHECK(!successAssemble("{ this }"));
BOOST_CHECK(!successAssemble("{ ecrecover }"));
CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found or not unique");
CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found or not unique");
BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover }"));
}
BOOST_AUTO_TEST_CASE(imbalanced_stack)
{
BOOST_CHECK(successAssemble("{ 1 2 mul pop }", false));
BOOST_CHECK(!successAssemble("{ 1 }", false));
CHECK_ASSEMBLE_ERROR("{ 1 }", Warning, "Inline assembly block is not balanced. It leaves");
CHECK_ASSEMBLE_ERROR("{ pop }", Warning, "Inline assembly block is not balanced. It takes");
BOOST_CHECK(successAssemble("{ let x := 4 7 add }", false));
}
@ -254,20 +321,17 @@ BOOST_AUTO_TEST_CASE(designated_invalid_instruction)
BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_declaration)
{
// Error message: "Cannot use instruction names for identifier names."
BOOST_CHECK(!successAssemble("{ let gas := 1 }"));
CHECK_ASSEMBLE_ERROR("{ let gas := 1 }", ParserError, "Cannot use instruction names for identifier names.");
}
BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_assignment)
{
// Error message: "Identifier expected, got instruction name."
BOOST_CHECK(!successAssemble("{ 2 =: gas }"));
CHECK_ASSEMBLE_ERROR("{ 2 =: gas }", ParserError, "Identifier expected, got instruction name.");
}
BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_functional_assignment)
{
// Error message: "Cannot use instruction names for identifier names."
BOOST_CHECK(!successAssemble("{ gas := 2 }"));
CHECK_ASSEMBLE_ERROR("{ gas := 2 }", ParserError, "Label name / variable name must precede \":\"");
}
BOOST_AUTO_TEST_CASE(revert)

View File

@ -4852,6 +4852,19 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack)
CHECK_WARNING(text, "Inline assembly block is not balanced");
}
BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load)
{
char const* text = R"(
contract c {
uint8 x;
function f() {
assembly { x pop }
}
}
)";
CHECK_WARNING(text, "Inline assembly block is not balanced");
}
BOOST_AUTO_TEST_CASE(inline_assembly_in_modifier)
{
char const* text = R"(
@ -5079,6 +5092,22 @@ BOOST_AUTO_TEST_CASE(invalid_address_length)
CHECK_WARNING(text, "checksum");
}
BOOST_AUTO_TEST_CASE(early_exit_on_fatal_errors)
{
// This tests a crash that occured because we did not stop for fatal errors.
char const* text = R"(
contract C {
struct S {
ftring a;
}
S public s;
function s() s {
}
}
)";
CHECK_ERROR(text, DeclarationError, "Identifier not found or not unique");
}
BOOST_AUTO_TEST_SUITE_END()
}