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: Bugfixes:
* Commandline interface: Always escape filenames (replace ``/``, ``:`` and ``.`` with ``_``). * Commandline interface: Always escape filenames (replace ``/``, ``:`` and ``.`` with ``_``).
* Commandline interface: Do not try creating paths ``.`` and ``..``. * 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: Disallow arrays with negative length.
* Type system: Fix a crash related to invalid binary operators. * 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) ### 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; mostSent = msg.value;
} }
function becomeRichest() returns (bool) { function becomeRichest() payable returns (bool) {
if (msg.value > mostSent) { if (msg.value > mostSent) {
// Check if call succeeds to prevent an attacker // Check if call succeeds to prevent an attacker
// from trapping the previous person's funds in // 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 install solidity
brew linkapps 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: .. _building-from-source:

View File

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

View File

@ -21,6 +21,7 @@
*/ */
#include <libsolidity/analysis/NameAndTypeResolver.h> #include <libsolidity/analysis/NameAndTypeResolver.h>
#include <libsolidity/ast/AST.h> #include <libsolidity/ast/AST.h>
#include <libsolidity/analysis/TypeChecker.h> #include <libsolidity/analysis/TypeChecker.h>
#include <libsolidity/interface/Exceptions.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 NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsideCode)
{ {
bool success = true;
try try
{ {
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(&_node)) return resolveNamesAndTypesInternal(_node, _resolveInsideCode);
{
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);
}
} }
catch (FatalError const&) catch (FatalError const&)
{ {
@ -193,7 +141,6 @@ bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsi
throw; // Something is weird here, rather throw again. throw; // Something is weird here, rather throw again.
return false; return false;
} }
return success;
} }
bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration) bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
@ -249,21 +196,25 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
solAssert(_declarations.size() > 1, ""); solAssert(_declarations.size() > 1, "");
vector<Declaration const*> uniqueFunctions; 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 // 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), solAssert(
"Found overloading involving something not a function or a variable"); 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) if (!functionType)
functionType = (*it)->functionType(true); functionType = declaration->functionType(true);
solAssert(functionType, "failed to determine the function type of the overloaded"); solAssert(functionType, "Failed to determine the function type of the overloaded.");
for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes()) for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes())
if (!parameter) 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( if (uniqueFunctions.end() == find_if(
uniqueFunctions.begin(), uniqueFunctions.begin(),
@ -276,11 +227,73 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
return newFunctionType && functionType->hasEqualArgumentTypes(*newFunctionType); return newFunctionType && functionType->hasEqualArgumentTypes(*newFunctionType);
} }
)) ))
uniqueFunctions.push_back(*it); uniqueFunctions.push_back(declaration);
} }
return uniqueFunctions; 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) void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base)
{ {
auto iterator = m_scopes.find(&_base); auto iterator = m_scopes.find(&_base);

View File

@ -89,6 +89,9 @@ public:
); );
private: 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) /// 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. /// into the current scope if they are not present already.
void importInheritedScope(ContractDefinition const& _base); void importInheritedScope(ContractDefinition const& _base);

View File

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

View File

@ -52,7 +52,7 @@ public:
m_resolveInsideCode(_resolveInsideCode) 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); bool resolve(ASTNode const& _root);
private: private:

View File

@ -611,7 +611,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly."); fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly.");
if (var->isLocalVariable()) if (var->isLocalVariable())
pushes = var->type()->sizeOnStack(); pushes = var->type()->sizeOnStack();
else if (var->type()->isValueType()) else if (!var->type()->isValueType())
pushes = 1; pushes = 1;
else else
pushes = 2; // slot number, intra slot offset 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()) for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
{ {
TypePointer type = variable->annotation().type; TypePointer type = variable->annotation().type;
solAssert(type, "");
// Skip all mapping members if we are not in storage. // Skip all mapping members if we are not in storage.
if (location() != DataLocation::Storage && !type->canLiveOutsideStorage()) if (location() != DataLocation::Storage && !type->canLiveOutsideStorage())
continue; continue;
@ -1967,6 +1968,8 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
if (auto structType = dynamic_cast<StructType const*>(returnType.get())) if (auto structType = dynamic_cast<StructType const*>(returnType.get()))
{ {
for (auto const& member: structType->members(nullptr)) for (auto const& member: structType->members(nullptr))
{
solAssert(member.type, "");
if (member.type->category() != Category::Mapping) if (member.type->category() != Category::Mapping)
{ {
if (auto arrayType = dynamic_cast<ArrayType const*>(member.type.get())) if (auto arrayType = dynamic_cast<ArrayType const*>(member.type.get()))
@ -1975,6 +1978,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
retParams.push_back(member.type); retParams.push_back(member.type);
retParamNames.push_back(member.name); retParamNames.push_back(member.name);
} }
}
} }
else else
{ {

View File

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

View File

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

View File

@ -62,6 +62,8 @@ assembly::Statement Parser::parseStatement()
{ {
case Token::Let: case Token::Let:
return parseVariableDeclaration(); return parseVariableDeclaration();
case Token::Function:
return parseFunctionDefinition();
case Token::LBrace: case Token::LBrace:
return parseBlock(); return parseBlock();
case Token::Assign: case Token::Assign:
@ -214,10 +216,7 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration()
{ {
VariableDeclaration varDecl = createWithLocation<VariableDeclaration>(); VariableDeclaration varDecl = createWithLocation<VariableDeclaration>();
expectToken(Token::Let); expectToken(Token::Let);
varDecl.name = m_scanner->currentLiteral(); varDecl.name = expectAsmIdentifier();
if (instructions().count(varDecl.name))
fatalParserError("Cannot use instruction names for identifier names.");
expectToken(Token::Identifier);
expectToken(Token::Colon); expectToken(Token::Colon);
expectToken(Token::Assign); expectToken(Token::Assign);
varDecl.value.reset(new Statement(parseExpression())); varDecl.value.reset(new Statement(parseExpression()));
@ -225,44 +224,107 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration()
return varDecl; return varDecl;
} }
FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement&& _instruction) assembly::FunctionDefinition Parser::parseFunctionDefinition()
{ {
if (_instruction.type() != typeid(Instruction)) FunctionDefinition funDef = createWithLocation<FunctionDefinition>();
fatalParserError("Assembly instruction required in front of \"(\")"); expectToken(Token::Function);
FunctionalInstruction ret; funDef.name = expectAsmIdentifier();
ret.instruction = std::move(boost::get<Instruction>(_instruction));
ret.location = ret.instruction.location;
solidity::Instruction instr = ret.instruction.instruction;
InstructionInfo instrInfo = instructionInfo(instr);
if (solidity::Instruction::DUP1 <= instr && instr <= solidity::Instruction::DUP16)
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); expectToken(Token::LParen);
unsigned args = unsigned(instrInfo.args); while (m_scanner->currentToken() != Token::RParen)
for (unsigned i = 0; i < args; ++i)
{ {
ret.arguments.emplace_back(parseExpression()); funDef.arguments.push_back(expectAsmIdentifier());
if (i != args - 1) if (m_scanner->currentToken() == Token::RParen)
{ break;
if (m_scanner->currentToken() != Token::Comma) expectToken(Token::Comma);
fatalParserError(string(
"Expected comma (" +
instrInfo.name +
" expects " +
boost::lexical_cast<string>(args) +
" arguments)"
));
else
m_scanner->next();
}
} }
ret.location.end = endPosition();
if (m_scanner->currentToken() == Token::Comma)
fatalParserError(
string("Expected ')' (" + instrInfo.name + " expects " + boost::lexical_cast<string>(args) + " arguments)")
);
expectToken(Token::RParen); expectToken(Token::RParen);
return ret; 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))
{
FunctionalInstruction ret;
ret.instruction = std::move(boost::get<Instruction>(_instruction));
ret.location = ret.instruction.location;
solidity::Instruction instr = ret.instruction.instruction;
InstructionInfo instrInfo = instructionInfo(instr);
if (solidity::Instruction::DUP1 <= instr && instr <= solidity::Instruction::DUP16)
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)
{
ret.arguments.emplace_back(parseExpression());
if (i != args - 1)
{
if (m_scanner->currentToken() != Token::Comma)
fatalParserError(string(
"Expected comma (" +
instrInfo.name +
" expects " +
boost::lexical_cast<string>(args) +
" arguments)"
));
else
m_scanner->next();
}
}
ret.location.end = endPosition();
if (m_scanner->currentToken() == Token::Comma)
fatalParserError(
string("Expected ')' (" + instrInfo.name + " expects " + boost::lexical_cast<string>(args) + " arguments)")
);
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(); std::map<std::string, dev::solidity::Instruction> const& instructions();
Statement parseElementaryOperation(bool _onlySinglePusher = false); Statement parseElementaryOperation(bool _onlySinglePusher = false);
VariableDeclaration parseVariableDeclaration(); 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); 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) string AsmPrinter::operator()(Block const& _block)
{ {
if (_block.statements.empty()) if (_block.statements.empty())

View File

@ -53,6 +53,8 @@ public:
std::string operator()(assembly::Assignment const& _assignment); std::string operator()(assembly::Assignment const& _assignment);
std::string operator()(assembly::FunctionalAssignment const& _functionalAssignment); std::string operator()(assembly::FunctionalAssignment const& _functionalAssignment);
std::string operator()(assembly::VariableDeclaration const& _variableDeclaration); 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); 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(contracts SRC_LIST)
aux_source_directory(liblll 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 get_filename_component(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE)
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)
file(GLOB HEADERS "*.h" "*/*.h") file(GLOB HEADERS "*.h" "*/*.h")
set(EXECUTABLE soltest) set(EXECUTABLE soltest)
@ -34,5 +20,5 @@ eth_use(${EXECUTABLE} REQUIRED Solidity::solidity Solidity::lll)
include_directories(BEFORE ..) include_directories(BEFORE ..)
target_link_libraries(${EXECUTABLE} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) target_link_libraries(${EXECUTABLE} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
enable_testing() add_executable(solfuzzer fuzzer.cpp)
set(CTEST_OUTPUT_ON_FAILURE TRUE) target_link_libraries(solfuzzer soljson)

View File

@ -31,7 +31,7 @@ set -e
REPO_ROOT="$(dirname "$0")"/.. REPO_ROOT="$(dirname "$0")"/..
SOLC="$REPO_ROOT/build/solc/solc" SOLC="$REPO_ROOT/build/solc/solc"
# Compile all files in std and examples. # Compile all files in std and examples.
for f in "$REPO_ROOT"/std/*.sol for f in "$REPO_ROOT"/std/*.sol
do do
@ -46,6 +46,21 @@ do
test -z "$output" -a "$failed" -eq 0 test -z "$output" -a "$failed" -eq 0
done done
# Test library checksum echo "Testing library checksum..."
echo 'contact C {}' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222 echo '' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222
! echo 'contract C {}' | "$SOLC" --link --libraries a:0x80f20564390eAe531E810af625A22f51385Cd222 2>/dev/null ! 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. * Unit tests for inline assembly.
*/ */
#include <string> #include "../TestHelper.h"
#include <memory>
#include <libevmasm/Assembly.h>
#include <libsolidity/parsing/Scanner.h>
#include <libsolidity/inlineasm/AsmStack.h> #include <libsolidity/inlineasm/AsmStack.h>
#include <libsolidity/parsing/Scanner.h>
#include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/Exceptions.h>
#include <libsolidity/ast/AST.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; using namespace std;
@ -41,31 +46,44 @@ namespace test
namespace 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; assembly::InlineAssemblyStack stack;
bool success = false;
try try
{ {
if (!stack.parse(std::make_shared<Scanner>(CharStream(_source)))) success = stack.parse(std::make_shared<Scanner>(CharStream(_source)));
return false; if (success && _assemble)
if (_assemble)
{
stack.assemble(); stack.assemble();
if (!stack.errors().empty())
if (!_allowWarnings || !Error::containsOnlyWarnings(stack.errors()))
return false;
}
} }
catch (FatalError const&) catch (FatalError const&)
{ {
if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError)) BOOST_FAIL("Fatal error leaked.");
return false; success = false;
} }
if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError)) if (!success)
return false; {
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 {};
}
BOOST_CHECK(Error::containsOnlyWarnings(stack.errors())); bool successParse(std::string const& _source, bool _assemble = false, bool _allowWarnings = true)
return true; {
return !parseAndReturnFirstError(_source, _assemble, _allowWarnings);
} }
bool successAssemble(string const& _source, bool _allowWarnings = true) 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); 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) void parsePrintCompare(string const& _source)
{ {
assembly::InlineAssemblyStack stack; 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) 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_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_END()
BOOST_AUTO_TEST_SUITE(Printing) BOOST_AUTO_TEST_SUITE(Printing)
@ -209,6 +265,16 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode)
parsePrintCompare(parsed); 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_END()
BOOST_AUTO_TEST_SUITE(Analysis) BOOST_AUTO_TEST_SUITE(Analysis)
@ -220,7 +286,7 @@ BOOST_AUTO_TEST_CASE(string_literals)
BOOST_AUTO_TEST_CASE(oversize_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) 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_AUTO_TEST_CASE(magic_variables)
{ {
BOOST_CHECK(!successAssemble("{ this }")); CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found or not unique");
BOOST_CHECK(!successAssemble("{ ecrecover }")); CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found or not unique");
BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover }")); BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover }"));
} }
BOOST_AUTO_TEST_CASE(imbalanced_stack) BOOST_AUTO_TEST_CASE(imbalanced_stack)
{ {
BOOST_CHECK(successAssemble("{ 1 2 mul pop }", false)); 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)); 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) BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_declaration)
{ {
// Error message: "Cannot use instruction names for identifier names." CHECK_ASSEMBLE_ERROR("{ let gas := 1 }", ParserError, "Cannot use instruction names for identifier names.");
BOOST_CHECK(!successAssemble("{ let gas := 1 }"));
} }
BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_assignment) BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_assignment)
{ {
// Error message: "Identifier expected, got instruction name." CHECK_ASSEMBLE_ERROR("{ 2 =: gas }", ParserError, "Identifier expected, got instruction name.");
BOOST_CHECK(!successAssemble("{ 2 =: gas }"));
} }
BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_functional_assignment) BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_functional_assignment)
{ {
// Error message: "Cannot use instruction names for identifier names." CHECK_ASSEMBLE_ERROR("{ gas := 2 }", ParserError, "Label name / variable name must precede \":\"");
BOOST_CHECK(!successAssemble("{ gas := 2 }"));
} }
BOOST_AUTO_TEST_CASE(revert) 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"); 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) BOOST_AUTO_TEST_CASE(inline_assembly_in_modifier)
{ {
char const* text = R"( char const* text = R"(
@ -5079,6 +5092,22 @@ BOOST_AUTO_TEST_CASE(invalid_address_length)
CHECK_WARNING(text, "checksum"); 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() BOOST_AUTO_TEST_SUITE_END()
} }