mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge branch 'develop' into fixNoMobile
This commit is contained in:
commit
92bf5154fd
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
|
||||||
@ -264,4 +280,4 @@ Example:
|
|||||||
3. a breaking change is introduced - version is bumped to 0.5.0
|
3. a breaking change is introduced - version is bumped to 0.5.0
|
||||||
4. the 0.5.0 release is made
|
4. the 0.5.0 release is made
|
||||||
|
|
||||||
This behaviour works well with the version pragma.
|
This behaviour works well with the version pragma.
|
||||||
|
@ -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() &&
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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:
|
||||||
|
@ -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>
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
92
test/fuzzer.cpp
Normal 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;
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user