aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorchriseth <chris@ethereum.org>2017-02-24 17:39:55 +0800
committerGitHub <noreply@github.com>2017-02-24 17:39:55 +0800
commit92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6 (patch)
tree4f5d35f34c7598e0640576376d9e4544378ed0f8
parentdcc16c81e26f31141ae766096873b5fd7741cdf5 (diff)
parentbec3c6fab6bf02aea5664be4423f45e98db22e8e (diff)
downloaddexon-solidity-92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6.tar
dexon-solidity-92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6.tar.gz
dexon-solidity-92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6.tar.bz2
dexon-solidity-92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6.tar.lz
dexon-solidity-92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6.tar.xz
dexon-solidity-92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6.tar.zst
dexon-solidity-92bf5154fdcf0dfee40bfb5795729a4a9fa71dd6.zip
Merge branch 'develop' into fixNoMobile
-rw-r--r--Changelog.md3
-rw-r--r--docs/common-patterns.rst2
-rw-r--r--docs/installing-solidity.rst18
-rw-r--r--libevmasm/Assembly.cpp2
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.cpp141
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.h3
-rw-r--r--libsolidity/analysis/ReferencesResolver.cpp9
-rw-r--r--libsolidity/analysis/ReferencesResolver.h2
-rw-r--r--libsolidity/analysis/TypeChecker.cpp2
-rw-r--r--libsolidity/ast/Types.cpp4
-rw-r--r--libsolidity/inlineasm/AsmCodeGen.cpp9
-rw-r--r--libsolidity/inlineasm/AsmData.h7
-rw-r--r--libsolidity/inlineasm/AsmParser.cpp138
-rw-r--r--libsolidity/inlineasm/AsmParser.h4
-rw-r--r--libsolidity/inlineasm/AsmPrinter.cpp18
-rw-r--r--libsolidity/inlineasm/AsmPrinter.h2
-rw-r--r--test/CMakeLists.txt22
-rwxr-xr-xtest/cmdlineTests.sh23
-rw-r--r--test/fuzzer.cpp92
-rw-r--r--test/libsolidity/InlineAssembly.cpp124
-rw-r--r--test/libsolidity/SolidityNameAndTypeResolution.cpp29
21 files changed, 484 insertions, 170 deletions
diff --git a/Changelog.md b/Changelog.md
index 5684adbd..19c3755b 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -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)
diff --git a/docs/common-patterns.rst b/docs/common-patterns.rst
index fa5e68a6..a2d7ce71 100644
--- a/docs/common-patterns.rst
+++ b/docs/common-patterns.rst
@@ -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
diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst
index 42905ede..fb405475 100644
--- a/docs/installing-solidity.rst
+++ b/docs/installing-solidity.rst
@@ -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:
@@ -264,4 +280,4 @@ Example:
3. a breaking change is introduced - version is bumped to 0.5.0
4. the 0.5.0 release is made
-This behaviour works well with the version pragma. \ No newline at end of file
+This behaviour works well with the version pragma.
diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp
index b7859c1f..f12e8aa8 100644
--- a/libevmasm/Assembly.cpp
+++ b/libevmasm/Assembly.cpp
@@ -130,8 +130,8 @@ public:
if (!_item.location().isEmpty() && _item.location() != m_location)
{
flush();
- printLocation();
m_location = _item.location();
+ printLocation();
}
if (!(
_item.canBeFunctional() &&
diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp
index 01384260..336dc894 100644
--- a/libsolidity/analysis/NameAndTypeResolver.cpp
+++ b/libsolidity/analysis/NameAndTypeResolver.cpp
@@ -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);
diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h
index 828b566f..038a887b 100644
--- a/libsolidity/analysis/NameAndTypeResolver.h
+++ b/libsolidity/analysis/NameAndTypeResolver.h
@@ -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);
diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp
index c06181d8..37bcb2d9 100644
--- a/libsolidity/analysis/ReferencesResolver.cpp
+++ b/libsolidity/analysis/ReferencesResolver.cpp
@@ -35,14 +35,7 @@ using namespace dev::solidity;
bool ReferencesResolver::resolve(ASTNode const& _root)
{
- try
- {
- _root.accept(*this);
- }
- catch (FatalError const&)
- {
- solAssert(m_errorOccurred, "");
- }
+ _root.accept(*this);
return !m_errorOccurred;
}
diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h
index 23ac6b07..dce343d3 100644
--- a/libsolidity/analysis/ReferencesResolver.h
+++ b/libsolidity/analysis/ReferencesResolver.h
@@ -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:
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index 28cb9acc..4025831e 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -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
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp
index 75dee6db..ff42cda9 100644
--- a/libsolidity/ast/Types.cpp
+++ b/libsolidity/ast/Types.cpp
@@ -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()))
@@ -1975,6 +1978,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
retParams.push_back(member.type);
retParamNames.push_back(member.name);
}
+ }
}
else
{
diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp
index 43c3b27a..faa7dabd 100644
--- a/libsolidity/inlineasm/AsmCodeGen.cpp
+++ b/libsolidity/inlineasm/AsmCodeGen.cpp
@@ -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:
diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h
index d622ff54..d61b5803 100644
--- a/libsolidity/inlineasm/AsmData.h
+++ b/libsolidity/inlineasm/AsmData.h
@@ -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>
{
diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp
index 46a2730d..0fc0a34f 100644
--- a/libsolidity/inlineasm/AsmParser.cpp
+++ b/libsolidity/inlineasm/AsmParser.cpp
@@ -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,44 +224,107 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration()
return varDecl;
}
-FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement&& _instruction)
+assembly::FunctionDefinition Parser::parseFunctionDefinition()
{
- 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;
- 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");
-
+ FunctionDefinition funDef = createWithLocation<FunctionDefinition>();
+ expectToken(Token::Function);
+ funDef.name = expectAsmIdentifier();
expectToken(Token::LParen);
- unsigned args = unsigned(instrInfo.args);
- for (unsigned i = 0; i < args; ++i)
+ while (m_scanner->currentToken() != Token::RParen)
{
- ret.arguments.emplace_back(parseExpression());
- if (i != args - 1)
+ 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)
{
- if (m_scanner->currentToken() != Token::Comma)
- fatalParserError(string(
- "Expected comma (" +
- instrInfo.name +
- " expects " +
- boost::lexical_cast<string>(args) +
- " arguments)"
- ));
- else
- m_scanner->next();
+ funDef.returns.push_back(expectAsmIdentifier());
+ if (m_scanner->currentToken() == Token::RParen)
+ break;
+ expectToken(Token::Comma);
}
+ expectToken(Token::RParen);
}
- 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;
+ 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;
}
diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h
index 643548dd..4b4a24ae 100644
--- a/libsolidity/inlineasm/AsmParser.h
+++ b/libsolidity/inlineasm/AsmParser.h
@@ -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();
};
}
diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp
index ab2a03ff..a70b0b78 100644
--- a/libsolidity/inlineasm/AsmPrinter.cpp
+++ b/libsolidity/inlineasm/AsmPrinter.cpp
@@ -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())
diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h
index 39069d02..a7a1de0a 100644
--- a/libsolidity/inlineasm/AsmPrinter.h
+++ b/libsolidity/inlineasm/AsmPrinter.h
@@ -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);
};
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 609aaab3..4d56ec9d 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -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)
diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh
index fc48654a..cb714efe 100755
--- a/test/cmdlineTests.sh
+++ b/test/cmdlineTests.sh
@@ -31,7 +31,7 @@ set -e
REPO_ROOT="$(dirname "$0")"/..
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
do
@@ -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."
diff --git a/test/fuzzer.cpp b/test/fuzzer.cpp
new file mode 100644
index 00000000..85a8fe99
--- /dev/null
+++ b/test/fuzzer.cpp
@@ -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;
+}
diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp
index 8744d96f..9035599b 100644
--- a/test/libsolidity/InlineAssembly.cpp
+++ b/test/libsolidity/InlineAssembly.cpp
@@ -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();
}
- if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError))
- return false;
+ 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()));
- 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)
diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp
index 1a4f3cdc..a1ebc300 100644
--- a/test/libsolidity/SolidityNameAndTypeResolution.cpp
+++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp
@@ -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()
}