aboutsummaryrefslogtreecommitdiffstats
path: root/libsolidity
diff options
context:
space:
mode:
Diffstat (limited to 'libsolidity')
-rw-r--r--libsolidity/analysis/ConstantEvaluator.cpp93
-rw-r--r--libsolidity/analysis/ConstantEvaluator.h20
-rw-r--r--libsolidity/analysis/DeclarationContainer.cpp22
-rw-r--r--libsolidity/analysis/DeclarationContainer.h4
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.cpp6
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.h3
-rw-r--r--libsolidity/analysis/PostTypeChecker.h2
-rw-r--r--libsolidity/analysis/ReferencesResolver.cpp55
-rw-r--r--libsolidity/analysis/StaticAnalyzer.h2
-rw-r--r--libsolidity/analysis/SyntaxChecker.cpp16
-rw-r--r--libsolidity/analysis/SyntaxChecker.h4
-rw-r--r--libsolidity/analysis/TypeChecker.cpp56
-rw-r--r--libsolidity/analysis/TypeChecker.h2
-rw-r--r--libsolidity/analysis/ViewPureChecker.cpp39
-rw-r--r--libsolidity/analysis/ViewPureChecker.h1
-rw-r--r--libsolidity/ast/AST.cpp11
-rw-r--r--libsolidity/ast/AST.h2
-rw-r--r--libsolidity/ast/ASTJsonConverter.cpp3
-rw-r--r--libsolidity/ast/ASTPrinter.cpp12
-rw-r--r--libsolidity/ast/ASTPrinter.h2
-rw-r--r--libsolidity/ast/Types.cpp20
-rw-r--r--libsolidity/ast/Types.h10
-rw-r--r--libsolidity/codegen/ABIFunctions.cpp459
-rw-r--r--libsolidity/codegen/ABIFunctions.h49
-rw-r--r--libsolidity/codegen/Compiler.cpp1
-rw-r--r--libsolidity/codegen/CompilerContext.cpp12
-rw-r--r--libsolidity/codegen/CompilerContext.h7
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp19
-rw-r--r--libsolidity/codegen/CompilerUtils.h7
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp80
-rw-r--r--libsolidity/codegen/ContractCompiler.h9
-rw-r--r--libsolidity/codegen/ExpressionCompiler.cpp30
-rw-r--r--libsolidity/formal/SMTChecker.cpp563
-rw-r--r--libsolidity/formal/SMTChecker.h78
-rw-r--r--libsolidity/formal/SMTLib2Interface.cpp11
-rw-r--r--libsolidity/formal/SMTLib2Interface.h8
-rw-r--r--libsolidity/formal/SolverInterface.h80
-rw-r--r--libsolidity/formal/VariableUsage.cpp80
-rw-r--r--libsolidity/formal/VariableUsage.h50
-rw-r--r--libsolidity/formal/Z3Interface.cpp16
-rw-r--r--libsolidity/inlineasm/AsmAnalysis.cpp61
-rw-r--r--libsolidity/inlineasm/AsmAnalysis.h10
-rw-r--r--libsolidity/inlineasm/AsmData.h18
-rw-r--r--libsolidity/inlineasm/AsmDataForward.h15
-rw-r--r--libsolidity/inlineasm/AsmParser.cpp163
-rw-r--r--libsolidity/inlineasm/AsmParser.h16
-rw-r--r--libsolidity/inlineasm/AsmPrinter.cpp18
-rw-r--r--libsolidity/inlineasm/AsmPrinter.h2
-rw-r--r--libsolidity/inlineasm/AsmScopeFiller.cpp16
-rw-r--r--libsolidity/inlineasm/AsmScopeFiller.h2
-rw-r--r--libsolidity/interface/AssemblyStack.cpp23
-rw-r--r--libsolidity/interface/AssemblyStack.h2
-rw-r--r--libsolidity/interface/CompilerStack.cpp9
-rw-r--r--libsolidity/interface/Exceptions.h12
-rw-r--r--libsolidity/interface/StandardCompiler.cpp131
-rw-r--r--libsolidity/parsing/Parser.cpp14
56 files changed, 2010 insertions, 446 deletions
diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp
index bc3b7cf1..83f37f47 100644
--- a/libsolidity/analysis/ConstantEvaluator.cpp
+++ b/libsolidity/analysis/ConstantEvaluator.cpp
@@ -28,49 +28,78 @@ using namespace std;
using namespace dev;
using namespace dev::solidity;
-/// FIXME: this is pretty much a copy of TypeChecker::endVisit(BinaryOperation)
void ConstantEvaluator::endVisit(UnaryOperation const& _operation)
{
- TypePointer const& subType = _operation.subExpression().annotation().type;
- if (!dynamic_cast<RationalNumberType const*>(subType.get()))
- m_errorReporter.fatalTypeError(_operation.subExpression().location(), "Invalid constant expression.");
- TypePointer t = subType->unaryOperatorResult(_operation.getOperator());
- _operation.annotation().type = t;
+ auto sub = type(_operation.subExpression());
+ if (sub)
+ setType(_operation, sub->unaryOperatorResult(_operation.getOperator()));
}
-/// FIXME: this is pretty much a copy of TypeChecker::endVisit(BinaryOperation)
void ConstantEvaluator::endVisit(BinaryOperation const& _operation)
{
- TypePointer const& leftType = _operation.leftExpression().annotation().type;
- TypePointer const& rightType = _operation.rightExpression().annotation().type;
- if (!dynamic_cast<RationalNumberType const*>(leftType.get()))
- m_errorReporter.fatalTypeError(_operation.leftExpression().location(), "Invalid constant expression.");
- if (!dynamic_cast<RationalNumberType const*>(rightType.get()))
- m_errorReporter.fatalTypeError(_operation.rightExpression().location(), "Invalid constant expression.");
- TypePointer commonType = leftType->binaryOperatorResult(_operation.getOperator(), rightType);
- if (!commonType)
+ auto left = type(_operation.leftExpression());
+ auto right = type(_operation.rightExpression());
+ if (left && right)
{
- m_errorReporter.typeError(
- _operation.location(),
- "Operator " +
- string(Token::toString(_operation.getOperator())) +
- " not compatible with types " +
- leftType->toString() +
- " and " +
- rightType->toString()
+ auto commonType = left->binaryOperatorResult(_operation.getOperator(), right);
+ if (!commonType)
+ m_errorReporter.fatalTypeError(
+ _operation.location(),
+ "Operator " +
+ string(Token::toString(_operation.getOperator())) +
+ " not compatible with types " +
+ left->toString() +
+ " and " +
+ right->toString()
+ );
+ setType(
+ _operation,
+ Token::isCompareOp(_operation.getOperator()) ?
+ make_shared<BoolType>() :
+ commonType
);
- commonType = leftType;
}
- _operation.annotation().commonType = commonType;
- _operation.annotation().type =
- Token::isCompareOp(_operation.getOperator()) ?
- make_shared<BoolType>() :
- commonType;
}
void ConstantEvaluator::endVisit(Literal const& _literal)
{
- _literal.annotation().type = Type::forLiteral(_literal);
- if (!_literal.annotation().type)
- m_errorReporter.fatalTypeError(_literal.location(), "Invalid literal value.");
+ setType(_literal, Type::forLiteral(_literal));
+}
+
+void ConstantEvaluator::endVisit(Identifier const& _identifier)
+{
+ VariableDeclaration const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration);
+ if (!variableDeclaration)
+ return;
+ if (!variableDeclaration->isConstant())
+ return;
+
+ ASTPointer<Expression> const& value = variableDeclaration->value();
+ if (!value)
+ return;
+ else if (!m_types->count(value.get()))
+ {
+ if (m_depth > 32)
+ m_errorReporter.fatalTypeError(_identifier.location(), "Cyclic constant definition (or maximum recursion depth exhausted).");
+ ConstantEvaluator(m_errorReporter, m_depth + 1, m_types).evaluate(*value);
+ }
+
+ setType(_identifier, type(*value));
+}
+
+void ConstantEvaluator::setType(ASTNode const& _node, TypePointer const& _type)
+{
+ if (_type && _type->category() == Type::Category::RationalNumber)
+ (*m_types)[&_node] = _type;
+}
+
+TypePointer ConstantEvaluator::type(ASTNode const& _node)
+{
+ return (*m_types)[&_node];
+}
+
+TypePointer ConstantEvaluator::evaluate(Expression const& _expr)
+{
+ _expr.accept(*this);
+ return type(_expr);
}
diff --git a/libsolidity/analysis/ConstantEvaluator.h b/libsolidity/analysis/ConstantEvaluator.h
index 90bceb5d..77a357b6 100644
--- a/libsolidity/analysis/ConstantEvaluator.h
+++ b/libsolidity/analysis/ConstantEvaluator.h
@@ -38,18 +38,32 @@ class TypeChecker;
class ConstantEvaluator: private ASTConstVisitor
{
public:
- ConstantEvaluator(Expression const& _expr, ErrorReporter& _errorReporter):
- m_errorReporter(_errorReporter)
+ ConstantEvaluator(
+ ErrorReporter& _errorReporter,
+ size_t _newDepth = 0,
+ std::shared_ptr<std::map<ASTNode const*, TypePointer>> _types = std::make_shared<std::map<ASTNode const*, TypePointer>>()
+ ):
+ m_errorReporter(_errorReporter),
+ m_depth(_newDepth),
+ m_types(_types)
{
- _expr.accept(*this);
}
+ TypePointer evaluate(Expression const& _expr);
+
private:
virtual void endVisit(BinaryOperation const& _operation);
virtual void endVisit(UnaryOperation const& _operation);
virtual void endVisit(Literal const& _literal);
+ virtual void endVisit(Identifier const& _identifier);
+
+ void setType(ASTNode const& _node, TypePointer const& _type);
+ TypePointer type(ASTNode const& _node);
ErrorReporter& m_errorReporter;
+ /// Current recursion depth.
+ size_t m_depth = 0;
+ std::shared_ptr<std::map<ASTNode const*, TypePointer>> m_types;
};
}
diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp
index b33c8568..7508ad9e 100644
--- a/libsolidity/analysis/DeclarationContainer.cpp
+++ b/libsolidity/analysis/DeclarationContainer.cpp
@@ -23,6 +23,7 @@
#include <libsolidity/analysis/DeclarationContainer.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/Types.h>
+#include <libdevcore/StringUtils.h>
using namespace std;
using namespace dev;
@@ -105,7 +106,7 @@ bool DeclarationContainer::registerDeclaration(
return true;
}
-std::vector<Declaration const*> DeclarationContainer::resolveName(ASTString const& _name, bool _recursive) const
+vector<Declaration const*> DeclarationContainer::resolveName(ASTString const& _name, bool _recursive) const
{
solAssert(!_name.empty(), "Attempt to resolve empty name.");
auto result = m_declarations.find(_name);
@@ -115,3 +116,22 @@ std::vector<Declaration const*> DeclarationContainer::resolveName(ASTString cons
return m_enclosingContainer->resolveName(_name, true);
return vector<Declaration const*>({});
}
+
+vector<ASTString> DeclarationContainer::similarNames(ASTString const& _name) const
+{
+ static size_t const MAXIMUM_EDIT_DISTANCE = 2;
+
+ vector<ASTString> similar;
+
+ for (auto const& declaration: m_declarations)
+ {
+ string const& declarationName = declaration.first;
+ if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE))
+ similar.push_back(declarationName);
+ }
+
+ if (m_enclosingContainer)
+ similar += m_enclosingContainer->similarNames(_name);
+
+ return similar;
+}
diff --git a/libsolidity/analysis/DeclarationContainer.h b/libsolidity/analysis/DeclarationContainer.h
index 301998b7..f9b1bda4 100644
--- a/libsolidity/analysis/DeclarationContainer.h
+++ b/libsolidity/analysis/DeclarationContainer.h
@@ -58,6 +58,10 @@ public:
/// @returns whether declaration is valid, and if not also returns previous declaration.
Declaration const* conflictingDeclaration(Declaration const& _declaration, ASTString const* _name = nullptr) const;
+ /// @returns existing declaration names similar to @a _name.
+ /// Searches this and all parent containers.
+ std::vector<ASTString> similarNames(ASTString const& _name) const;
+
private:
ASTNode const* m_enclosingNode;
DeclarationContainer const* m_enclosingContainer;
diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp
index 5d010693..5e4d414b 100644
--- a/libsolidity/analysis/NameAndTypeResolver.cpp
+++ b/libsolidity/analysis/NameAndTypeResolver.cpp
@@ -25,6 +25,7 @@
#include <libsolidity/ast/AST.h>
#include <libsolidity/analysis/TypeChecker.h>
#include <libsolidity/interface/ErrorReporter.h>
+#include <libdevcore/StringUtils.h>
#include <boost/algorithm/string.hpp>
@@ -425,6 +426,11 @@ vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMer
return result;
}
+string NameAndTypeResolver::similarNameSuggestions(ASTString const& _name) const
+{
+ return quotedAlternativesList(m_currentScope->similarNames(_name));
+}
+
DeclarationRegistrationHelper::DeclarationRegistrationHelper(
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
ASTNode& _astRoot,
diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h
index d83697cd..9aea07ab 100644
--- a/libsolidity/analysis/NameAndTypeResolver.h
+++ b/libsolidity/analysis/NameAndTypeResolver.h
@@ -93,6 +93,9 @@ public:
/// Generate and store warnings about variables that are named like instructions.
void warnVariablesNamedLikeInstructions();
+ /// @returns a list of similar identifiers in the current and enclosing scopes. May return empty string if no suggestions.
+ std::string similarNameSuggestions(ASTString const& _name) const;
+
private:
/// Internal version of @a resolveNamesAndTypes (called from there) throws exceptions on fatal errors.
bool resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode = true);
diff --git a/libsolidity/analysis/PostTypeChecker.h b/libsolidity/analysis/PostTypeChecker.h
index 91d2b0b9..bafc1ae6 100644
--- a/libsolidity/analysis/PostTypeChecker.h
+++ b/libsolidity/analysis/PostTypeChecker.h
@@ -38,7 +38,7 @@ class ErrorReporter;
class PostTypeChecker: private ASTConstVisitor
{
public:
- /// @param _errors the reference to the list of errors and warnings to add them found during type checking.
+ /// @param _errorReporter provides the error logging functionality.
PostTypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
bool check(ASTNode const& _astRoot);
diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp
index f22c95cc..0bb5e3fe 100644
--- a/libsolidity/analysis/ReferencesResolver.cpp
+++ b/libsolidity/analysis/ReferencesResolver.cpp
@@ -47,7 +47,13 @@ bool ReferencesResolver::visit(Identifier const& _identifier)
{
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name());
if (declarations.empty())
- fatalDeclarationError(_identifier.location(), "Undeclared identifier.");
+ {
+ string suggestions = m_resolver.similarNameSuggestions(_identifier.name());
+ string errorMessage =
+ "Undeclared identifier." +
+ (suggestions.empty()? "": " Did you mean " + std::move(suggestions) + "?");
+ declarationError(_identifier.location(), errorMessage);
+ }
else if (declarations.size() == 1)
_identifier.annotation().referencedDeclaration = declarations.front();
else
@@ -90,7 +96,10 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
{
Declaration const* declaration = m_resolver.pathFromCurrentScope(_typeName.namePath());
if (!declaration)
- fatalDeclarationError(_typeName.location(), "Identifier not found or not unique.");
+ {
+ declarationError(_typeName.location(), "Identifier not found or not unique.");
+ return;
+ }
_typeName.annotation().referencedDeclaration = declaration;
@@ -101,7 +110,7 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
_typeName.annotation().type = make_shared<ContractType>(*contract);
else
- fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
+ typeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
}
void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
@@ -112,17 +121,25 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
case VariableDeclaration::Visibility::External:
break;
default:
- fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
+ typeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
+ return;
}
if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External)
- fatalTypeError(_typeName.location(), "Only external function types can be payable.");
+ {
+ typeError(_typeName.location(), "Only external function types can be payable.");
+ return;
+ }
+
if (_typeName.visibility() == VariableDeclaration::Visibility::External)
for (auto const& t: _typeName.parameterTypes() + _typeName.returnParameterTypes())
{
solAssert(t->annotation().type, "Type not set for parameter.");
if (!t->annotation().type->canBeUsedExternally(false))
- fatalTypeError(t->location(), "Internal type cannot be used for external function type.");
+ {
+ typeError(t->location(), "Internal type cannot be used for external function type.");
+ return;
+ }
}
_typeName.annotation().type = make_shared<FunctionType>(_typeName);
@@ -142,15 +159,21 @@ void ReferencesResolver::endVisit(Mapping const& _typeName)
void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
{
TypePointer baseType = _typeName.baseType().annotation().type;
+ if (!baseType)
+ {
+ solAssert(!m_errorReporter.errors().empty(), "");
+ return;
+ }
if (baseType->storageBytes() == 0)
fatalTypeError(_typeName.baseType().location(), "Illegal base type of storage size zero for array.");
if (Expression const* length = _typeName.length())
{
- if (!length->annotation().type)
- ConstantEvaluator e(*length, m_errorReporter);
- auto const* lengthType = dynamic_cast<RationalNumberType const*>(length->annotation().type.get());
+ TypePointer lengthTypeGeneric = length->annotation().type;
+ if (!lengthTypeGeneric)
+ lengthTypeGeneric = ConstantEvaluator(m_errorReporter).evaluate(*length);
+ RationalNumberType const* lengthType = dynamic_cast<RationalNumberType const*>(lengthTypeGeneric.get());
if (!lengthType || !lengthType->mobileType())
- fatalTypeError(length->location(), "Invalid array length, expected integer literal.");
+ fatalTypeError(length->location(), "Invalid array length, expected integer literal or constant expression.");
else if (lengthType->isFractional())
fatalTypeError(length->location(), "Array with fractional length specified.");
else if (lengthType->isNegative())
@@ -206,7 +229,7 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
// Will be re-generated later with correct information
assembly::AsmAnalysisInfo analysisInfo;
- assembly::AsmAnalyzer(analysisInfo, errorsIgnored, false, resolver).analyze(_inlineAssembly.operations());
+ assembly::AsmAnalyzer(analysisInfo, errorsIgnored, assembly::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations());
return false;
}
@@ -321,17 +344,13 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
type = ref->copyForLocation(typeLoc, isPointer);
}
else if (varLoc != Location::Default && !ref)
- fatalTypeError(_variable.location(), "Storage location can only be given for array or struct types.");
-
- if (!type)
- fatalTypeError(_variable.location(), "Invalid type name.");
+ typeError(_variable.location(), "Storage location can only be given for array or struct types.");
+ _variable.annotation().type = type;
}
else if (!_variable.canHaveAutoType())
- fatalTypeError(_variable.location(), "Explicit type needed.");
+ typeError(_variable.location(), "Explicit type needed.");
// otherwise we have a "var"-declaration whose type is resolved by the first assignment
-
- _variable.annotation().type = type;
}
void ReferencesResolver::typeError(SourceLocation const& _location, string const& _description)
diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h
index 24ed119f..124c4e7c 100644
--- a/libsolidity/analysis/StaticAnalyzer.h
+++ b/libsolidity/analysis/StaticAnalyzer.h
@@ -43,7 +43,7 @@ namespace solidity
class StaticAnalyzer: private ASTConstVisitor
{
public:
- /// @param _errors the reference to the list of errors and warnings to add them found during static analysis.
+ /// @param _errorReporter provides the error logging functionality.
explicit StaticAnalyzer(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
/// Performs static analysis on the given source unit and all of its sub-nodes.
diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp
index 0ca4b86c..5a3745b0 100644
--- a/libsolidity/analysis/SyntaxChecker.cpp
+++ b/libsolidity/analysis/SyntaxChecker.cpp
@@ -54,7 +54,7 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit)
string(".") +
to_string(recommendedVersion.minor()) +
string(".") +
- to_string(recommendedVersion.patch());
+ to_string(recommendedVersion.patch()) +
string(";\"");
m_errorReporter.warning(_sourceUnit.location(), errorString);
@@ -224,3 +224,17 @@ bool SyntaxChecker::visit(FunctionTypeName const& _node)
return true;
}
+
+bool SyntaxChecker::visit(VariableDeclaration const& _declaration)
+{
+ bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050);
+
+ if (!_declaration.typeName())
+ {
+ if (v050)
+ m_errorReporter.syntaxError(_declaration.location(), "Use of the \"var\" keyword is deprecated.");
+ else
+ m_errorReporter.warning(_declaration.location(), "Use of the \"var\" keyword is deprecated.");
+ }
+ return true;
+}
diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h
index 7fffbec0..871bf0a9 100644
--- a/libsolidity/analysis/SyntaxChecker.h
+++ b/libsolidity/analysis/SyntaxChecker.h
@@ -38,7 +38,7 @@ namespace solidity
class SyntaxChecker: private ASTConstVisitor
{
public:
- /// @param _errors the reference to the list of errors and warnings to add them found during type checking.
+ /// @param _errorReporter provides the error logging functionality.
SyntaxChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
bool checkSyntax(ASTNode const& _astRoot);
@@ -69,6 +69,8 @@ private:
virtual bool visit(FunctionDefinition const& _function) override;
virtual bool visit(FunctionTypeName const& _node) override;
+ virtual bool visit(VariableDeclaration const& _declaration) override;
+
ErrorReporter& m_errorReporter;
/// Flag that indicates whether a function modifier actually contains '_'.
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index 746e762e..f62ddeb9 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -171,13 +171,7 @@ void TypeChecker::checkContractDuplicateFunctions(ContractDefinition const& _con
ssl.append("Another declaration is here:", (*it)->location());
string msg = "More than one constructor defined.";
- size_t occurrences = ssl.infos.size();
- if (occurrences > 32)
- {
- ssl.infos.resize(32);
- msg += " Truncated from " + boost::lexical_cast<string>(occurrences) + " to the first 32 occurrences.";
- }
-
+ ssl.limitSize(msg);
m_errorReporter.declarationError(
functions[_contract.name()].front()->location(),
ssl,
@@ -219,12 +213,7 @@ void TypeChecker::findDuplicateDefinitions(map<string, vector<T>> const& _defini
if (ssl.infos.size() > 0)
{
- size_t occurrences = ssl.infos.size();
- if (occurrences > 32)
- {
- ssl.infos.resize(32);
- _message += " Truncated from " + boost::lexical_cast<string>(occurrences) + " to the first 32 occurrences.";
- }
+ ssl.limitSize(_message);
m_errorReporter.declarationError(
overloads[i]->location(),
@@ -570,6 +559,17 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction)))
m_errorReporter.fatalTypeError(var->location(), "Internal or recursive type is not allowed for public or external functions.");
+ if (
+ _function.visibility() > FunctionDefinition::Visibility::Internal &&
+ type(*var)->category() == Type::Category::Struct &&
+ !type(*var)->dataStoredIn(DataLocation::Storage) &&
+ !_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2)
+ )
+ m_errorReporter.typeError(
+ var->location(),
+ "Structs are only supported in the new experimental ABI encoder. "
+ "Use \"pragma experimental ABIEncoderV2;\" to enable the feature."
+ );
var->accept(*this);
}
@@ -604,6 +604,8 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
{
if (_function.visibility() < FunctionDefinition::Visibility::Public)
m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot be internal or private.");
+ else if (_function.visibility() != FunctionDefinition::Visibility::External)
+ m_errorReporter.warning(_function.location(), "Functions in interfaces should be declared external.");
}
if (_function.isConstructor())
m_errorReporter.typeError(_function.location(), "Constructor cannot be defined in interfaces.");
@@ -873,7 +875,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
assembly::AsmAnalyzer analyzer(
*_inlineAssembly.annotation().analysisInfo,
m_errorReporter,
- false,
+ assembly::AsmFlavour::Loose,
identifierAccess
);
if (!analyzer.analyze(_inlineAssembly.operations()))
@@ -970,7 +972,11 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
string errorText{"Uninitialized storage pointer."};
if (varDecl.referenceLocation() == VariableDeclaration::Location::Default)
errorText += " Did you mean '<type> memory " + varDecl.name() + "'?";
- m_errorReporter.warning(varDecl.location(), errorText);
+ solAssert(m_scope, "");
+ if (m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050))
+ m_errorReporter.declarationError(varDecl.location(), errorText);
+ else
+ m_errorReporter.warning(varDecl.location(), errorText);
}
}
else if (dynamic_cast<MappingType const*>(type(varDecl).get()))
@@ -1060,7 +1066,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
_statement.initialValue()->location(),
"Invalid rational " +
valueComponentType->toString() +
- " (absolute value too large or divison by zero)."
+ " (absolute value too large or division by zero)."
);
else
solAssert(false, "");
@@ -1122,7 +1128,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
var.annotation().type->toString() +
". Try converting to type " +
valueComponentType->mobileType()->toString() +
- " or use an explicit conversion."
+ " or use an explicit conversion."
);
else
m_errorReporter.typeError(
@@ -1320,7 +1326,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
_tuple.annotation().isPure = isPure;
if (_tuple.isInlineArray())
{
- if (!inlineArrayType)
+ if (!inlineArrayType)
m_errorReporter.fatalTypeError(_tuple.location(), "Unable to deduce common type for array elements.");
_tuple.annotation().type = make_shared<ArrayType>(DataLocation::Memory, inlineArrayType, types.size());
}
@@ -1551,8 +1557,12 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
if (!functionType->takesArbitraryParameters() && parameterTypes.size() != arguments.size())
{
+ bool isStructConstructorCall = _functionCall.annotation().kind == FunctionCallKind::StructConstructorCall;
+
string msg =
- "Wrong argument count for function call: " +
+ "Wrong argument count for " +
+ string(isStructConstructorCall ? "struct constructor" : "function call") +
+ ": " +
toString(arguments.size()) +
" arguments given but expected " +
toString(parameterTypes.size()) +
@@ -1668,10 +1678,12 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
SecondarySourceLocation ssl;
for (auto function: contract->annotation().unimplementedFunctions)
ssl.append("Missing implementation:", function->location());
+ string msg = "Trying to create an instance of an abstract contract.";
+ ssl.limitSize(msg);
m_errorReporter.typeError(
_newExpression.location(),
ssl,
- "Trying to create an instance of an abstract contract."
+ msg
);
}
if (!contract->constructorIsPublic())
@@ -2000,7 +2012,9 @@ void TypeChecker::endVisit(Literal const& _literal)
m_errorReporter.warning(
_literal.location(),
"This looks like an address but has an invalid checksum. "
- "If this is not used as an address, please prepend '00'."
+ "If this is not used as an address, please prepend '00'. " +
+ (!_literal.getChecksummedAddress().empty() ? "Correct checksummed address: '" + _literal.getChecksummedAddress() + "'. " : "") +
+ "For more information please see https://solidity.readthedocs.io/en/develop/types.html#address-literals"
);
}
if (!_literal.annotation().type)
diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h
index abe6dac1..344b019d 100644
--- a/libsolidity/analysis/TypeChecker.h
+++ b/libsolidity/analysis/TypeChecker.h
@@ -42,7 +42,7 @@ class ErrorReporter;
class TypeChecker: private ASTConstVisitor
{
public:
- /// @param _errors the reference to the list of errors and warnings to add them found during type checking.
+ /// @param _errorReporter provides the error logging functionality.
TypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
/// Performs type checking on the given contract and all of its sub-nodes.
diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp
index 7f28c7d2..13c3ab68 100644
--- a/libsolidity/analysis/ViewPureChecker.cpp
+++ b/libsolidity/analysis/ViewPureChecker.cpp
@@ -40,19 +40,20 @@ public:
void operator()(assembly::Label const&) { }
void operator()(assembly::Instruction const& _instruction)
{
- if (eth::SemanticInformation::invalidInViewFunctions(_instruction.instruction))
- m_reportMutability(StateMutability::NonPayable, _instruction.location);
- else if (eth::SemanticInformation::invalidInPureFunctions(_instruction.instruction))
- m_reportMutability(StateMutability::View, _instruction.location);
+ checkInstruction(_instruction.location, _instruction.instruction);
}
void operator()(assembly::Literal const&) {}
void operator()(assembly::Identifier const&) {}
void operator()(assembly::FunctionalInstruction const& _instr)
{
- (*this)(_instr.instruction);
+ checkInstruction(_instr.location, _instr.instruction);
for (auto const& arg: _instr.arguments)
boost::apply_visitor(*this, arg);
}
+ void operator()(assembly::ExpressionStatement const& _expr)
+ {
+ boost::apply_visitor(*this, _expr.expression);
+ }
void operator()(assembly::StackAssignment const&) {}
void operator()(assembly::Assignment const& _assignment)
{
@@ -72,6 +73,11 @@ public:
for (auto const& arg: _funCall.arguments)
boost::apply_visitor(*this, arg);
}
+ void operator()(assembly::If const& _if)
+ {
+ boost::apply_visitor(*this, *_if.condition);
+ (*this)(_if.body);
+ }
void operator()(assembly::Switch const& _switch)
{
boost::apply_visitor(*this, *_switch.expression);
@@ -97,6 +103,13 @@ public:
private:
std::function<void(StateMutability, SourceLocation const&)> m_reportMutability;
+ void checkInstruction(SourceLocation _location, solidity::Instruction _instruction)
+ {
+ if (eth::SemanticInformation::invalidInViewFunctions(_instruction))
+ m_reportMutability(StateMutability::NonPayable, _location);
+ else if (eth::SemanticInformation::invalidInPureFunctions(_instruction))
+ m_reportMutability(StateMutability::View, _location);
+ }
};
}
@@ -262,6 +275,22 @@ void ViewPureChecker::endVisit(FunctionCall const& _functionCall)
reportMutability(mut, _functionCall.location());
}
+bool ViewPureChecker::visit(MemberAccess const& _memberAccess)
+{
+ // Catch the special case of `this.f.selector` which is a pure expression.
+ ASTString const& member = _memberAccess.memberName();
+ if (
+ _memberAccess.expression().annotation().type->category() == Type::Category::Function &&
+ member == "selector"
+ )
+ if (auto const* expr = dynamic_cast<MemberAccess const*>(&_memberAccess.expression()))
+ if (auto const* exprInt = dynamic_cast<Identifier const*>(&expr->expression()))
+ if (exprInt->name() == "this")
+ // Do not continue visiting.
+ return false;
+ return true;
+}
+
void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
{
StateMutability mutability = StateMutability::Pure;
diff --git a/libsolidity/analysis/ViewPureChecker.h b/libsolidity/analysis/ViewPureChecker.h
index fec060b6..0b882cd8 100644
--- a/libsolidity/analysis/ViewPureChecker.h
+++ b/libsolidity/analysis/ViewPureChecker.h
@@ -56,6 +56,7 @@ private:
virtual bool visit(ModifierDefinition const& _modifierDef) override;
virtual void endVisit(ModifierDefinition const& _modifierDef) override;
virtual void endVisit(Identifier const& _identifier) override;
+ virtual bool visit(MemberAccess const& _memberAccess) override;
virtual void endVisit(MemberAccess const& _memberAccess) override;
virtual void endVisit(IndexAccess const& _indexAccess) override;
virtual void endVisit(ModifierInvocation const& _modifier) override;
diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp
index 1048b610..8da6964e 100644
--- a/libsolidity/ast/AST.cpp
+++ b/libsolidity/ast/AST.cpp
@@ -583,3 +583,14 @@ bool Literal::passesAddressChecksum() const
solAssert(isHexNumber(), "Expected hex number");
return dev::passesAddressChecksum(value(), true);
}
+
+std::string Literal::getChecksummedAddress() const
+{
+ solAssert(isHexNumber(), "Expected hex number");
+ /// Pad literal to be a proper hex address.
+ string address = value().substr(2);
+ if (address.length() > 40)
+ return string();
+ address.insert(address.begin(), 40 - address.size(), '0');
+ return dev::getChecksummedAddress(address);
+}
diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h
index 733e7c78..feffde64 100644
--- a/libsolidity/ast/AST.h
+++ b/libsolidity/ast/AST.h
@@ -1613,6 +1613,8 @@ public:
bool looksLikeAddress() const;
/// @returns true if it passes the address checksum test.
bool passesAddressChecksum() const;
+ /// @returns the checksummed version of an address (or empty string if not valid)
+ std::string getChecksummedAddress() const;
private:
Token::Value m_token;
diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp
index 51249f20..cd9f7eca 100644
--- a/libsolidity/ast/ASTJsonConverter.cpp
+++ b/libsolidity/ast/ASTJsonConverter.cpp
@@ -324,6 +324,7 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node)
{
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
+ make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue),
// FIXME: remove with next breaking release
make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.stateMutability() <= StateMutability::View),
make_pair("payable", _node.isPayable()),
@@ -365,6 +366,7 @@ bool ASTJsonConverter::visit(ModifierDefinition const& _node)
{
setJsonNode(_node, "ModifierDefinition", {
make_pair("name", _node.name()),
+ make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue),
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("parameters", toJson(_node.parameterList())),
make_pair("body", toJson(_node.body()))
@@ -386,6 +388,7 @@ bool ASTJsonConverter::visit(EventDefinition const& _node)
m_inEvent = true;
setJsonNode(_node, "EventDefinition", {
make_pair("name", _node.name()),
+ make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue),
make_pair("parameters", toJson(_node.parameterList())),
make_pair("anonymous", _node.isAnonymous())
});
diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp
index 81e6cc44..23c3cbe1 100644
--- a/libsolidity/ast/ASTPrinter.cpp
+++ b/libsolidity/ast/ASTPrinter.cpp
@@ -78,6 +78,13 @@ bool ASTPrinter::visit(InheritanceSpecifier const& _node)
return goDeeper();
}
+bool ASTPrinter::visit(UsingForDirective const& _node)
+{
+ writeLine("UsingForDirective");
+ printSourcePart(_node);
+ return goDeeper();
+}
+
bool ASTPrinter::visit(StructDefinition const& _node)
{
writeLine("StructDefinition \"" + _node.name() + "\"");
@@ -385,6 +392,11 @@ void ASTPrinter::endVisit(InheritanceSpecifier const&)
m_indentation--;
}
+void ASTPrinter::endVisit(UsingForDirective const&)
+{
+ m_indentation--;
+}
+
void ASTPrinter::endVisit(StructDefinition const&)
{
m_indentation--;
diff --git a/libsolidity/ast/ASTPrinter.h b/libsolidity/ast/ASTPrinter.h
index d6897dfd..01e4f7fc 100644
--- a/libsolidity/ast/ASTPrinter.h
+++ b/libsolidity/ast/ASTPrinter.h
@@ -51,6 +51,7 @@ public:
bool visit(ImportDirective const& _node) override;
bool visit(ContractDefinition const& _node) override;
bool visit(InheritanceSpecifier const& _node) override;
+ bool visit(UsingForDirective const& _node) override;
bool visit(StructDefinition const& _node) override;
bool visit(EnumDefinition const& _node) override;
bool visit(EnumValue const& _node) override;
@@ -94,6 +95,7 @@ public:
void endVisit(ImportDirective const&) override;
void endVisit(ContractDefinition const&) override;
void endVisit(InheritanceSpecifier const&) override;
+ void endVisit(UsingForDirective const&) override;
void endVisit(StructDefinition const&) override;
void endVisit(EnumDefinition const&) override;
void endVisit(EnumValue const&) override;
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp
index ee5f462b..e4b7e4fd 100644
--- a/libsolidity/ast/Types.cpp
+++ b/libsolidity/ast/Types.cpp
@@ -949,11 +949,25 @@ bool RationalNumberType::operator==(Type const& _other) const
return m_value == other.m_value;
}
+string RationalNumberType::bigintToReadableString(dev::bigint const& _num)
+{
+ string str = _num.str();
+ if (str.size() > 32)
+ {
+ int omitted = str.size() - 8;
+ str = str.substr(0, 4) + "...(" + to_string(omitted) + " digits omitted)..." + str.substr(str.size() - 4, 4);
+ }
+ return str;
+}
+
string RationalNumberType::toString(bool) const
{
if (!isFractional())
- return "int_const " + m_value.numerator().str();
- return "rational_const " + m_value.numerator().str() + '/' + m_value.denominator().str();
+ return "int_const " + bigintToReadableString(m_value.numerator());
+
+ string numerator = bigintToReadableString(m_value.numerator());
+ string denominator = bigintToReadableString(m_value.denominator());
+ return "rational_const " + numerator + " / " + denominator;
}
u256 RationalNumberType::literalValue(Literal const*) const
@@ -2574,7 +2588,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
"selector",
make_shared<FixedBytesType>(4)
));
- if (m_kind != Kind::BareDelegateCall && m_kind != Kind::DelegateCall)
+ if (m_kind != Kind::BareDelegateCall)
{
if (isPayable())
members.push_back(MemberList::Member(
diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h
index ce29975e..2e7d05ba 100644
--- a/libsolidity/ast/Types.h
+++ b/libsolidity/ast/Types.h
@@ -257,7 +257,7 @@ public:
}
virtual u256 literalValue(Literal const*) const
{
- solAssert(false, "Literal value requested for type without literals.");
+ solAssert(false, "Literal value requested for type without literals: " + toString(false));
}
/// @returns a (simpler) type that is encoded in the same way for external function calls.
@@ -437,6 +437,10 @@ private:
/// @returns true if the literal is a valid rational number.
static std::tuple<bool, rational> parseRational(std::string const& _value);
+
+ /// @returns a truncated readable representation of the bigint keeping only
+ /// up to 4 leading and 4 trailing digits.
+ static std::string bigintToReadableString(dev::bigint const& num);
};
/**
@@ -707,10 +711,6 @@ public:
/// Returns the function type of the constructor modified to return an object of the contract's type.
FunctionTypePointer const& newExpressionType() const;
- /// @returns the identifier of the function with the given name or Invalid256 if such a name does
- /// not exist.
- u256 functionIdentifier(std::string const& _functionName) const;
-
/// @returns a list of all state variables (including inherited) of the contract and their
/// offsets in storage.
std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const;
diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp
index 080be359..00f59065 100644
--- a/libsolidity/codegen/ABIFunctions.cpp
+++ b/libsolidity/codegen/ABIFunctions.cpp
@@ -22,9 +22,13 @@
#include <libsolidity/codegen/ABIFunctions.h>
+#include <libsolidity/ast/AST.h>
+#include <libsolidity/codegen/CompilerUtils.h>
+
#include <libdevcore/Whiskers.h>
-#include <libsolidity/ast/AST.h>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/range/adaptor/reversed.hpp>
using namespace std;
using namespace dev;
@@ -99,6 +103,79 @@ string ABIFunctions::tupleEncoder(
});
}
+string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
+{
+ string functionName = string("abi_decode_tuple_");
+ for (auto const& t: _types)
+ functionName += t->identifier();
+ if (_fromMemory)
+ functionName += "_fromMemory";
+
+ solAssert(!_types.empty(), "");
+
+ return createFunction(functionName, [&]() {
+ TypePointers decodingTypes;
+ for (auto const& t: _types)
+ decodingTypes.emplace_back(t->decodingType());
+
+ Whiskers templ(R"(
+ function <functionName>(headStart, dataEnd) -> <valueReturnParams> {
+ if slt(sub(dataEnd, headStart), <minimumSize>) { revert(0, 0) }
+ <decodeElements>
+ }
+ )");
+ templ("functionName", functionName);
+ templ("minimumSize", to_string(headSize(decodingTypes)));
+
+ string decodeElements;
+ vector<string> valueReturnParams;
+ size_t headPos = 0;
+ size_t stackPos = 0;
+ for (size_t i = 0; i < _types.size(); ++i)
+ {
+ solAssert(_types[i], "");
+ solAssert(decodingTypes[i], "");
+ size_t sizeOnStack = _types[i]->sizeOnStack();
+ solAssert(sizeOnStack == decodingTypes[i]->sizeOnStack(), "");
+ solAssert(sizeOnStack > 0, "");
+ vector<string> valueNamesLocal;
+ for (size_t j = 0; j < sizeOnStack; j++)
+ {
+ valueNamesLocal.push_back("value" + to_string(stackPos));
+ valueReturnParams.push_back("value" + to_string(stackPos));
+ stackPos++;
+ }
+ bool dynamic = decodingTypes[i]->isDynamicallyEncoded();
+ Whiskers elementTempl(
+ dynamic ?
+ R"(
+ {
+ let offset := <load>(add(headStart, <pos>))
+ if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
+ <values> := <abiDecode>(add(headStart, offset), dataEnd)
+ }
+ )" :
+ R"(
+ {
+ let offset := <pos>
+ <values> := <abiDecode>(add(headStart, offset), dataEnd)
+ }
+ )"
+ );
+ elementTempl("load", _fromMemory ? "mload" : "calldataload");
+ elementTempl("values", boost::algorithm::join(valueNamesLocal, ", "));
+ elementTempl("pos", to_string(headPos));
+ elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true));
+ decodeElements += elementTempl.render();
+ headPos += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize();
+ }
+ templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", "));
+ templ("decodeElements", decodeElements);
+
+ return templ.render();
+ });
+}
+
string ABIFunctions::requestedFunctions()
{
string result;
@@ -141,10 +218,9 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
solUnimplemented("Fixed point types not implemented.");
break;
case Type::Category::Array:
- solAssert(false, "Array cleanup requested.");
- break;
case Type::Category::Struct:
- solAssert(false, "Struct cleanup requested.");
+ solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type.");
+ templ("body", "cleaned := value");
break;
case Type::Category::FixedBytes:
{
@@ -168,7 +244,7 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
{
size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers();
solAssert(members > 0, "empty enum should have caused a parser error.");
- Whiskers w("switch lt(value, <members>) case 0 { <failure> } cleaned := value");
+ Whiskers w("if iszero(lt(value, <members>)) { <failure> } cleaned := value");
w("members", to_string(members));
if (_revertOnFailure)
w("failure", "revert(0, 0)");
@@ -367,6 +443,24 @@ string ABIFunctions::combineExternalFunctionIdFunction()
});
}
+string ABIFunctions::splitExternalFunctionIdFunction()
+{
+ string functionName = "split_external_function_id";
+ return createFunction(functionName, [&]() {
+ return Whiskers(R"(
+ function <functionName>(combined) -> addr, selector {
+ combined := <shr64>(combined)
+ selector := and(combined, 0xffffffff)
+ addr := <shr32>(combined)
+ }
+ )")
+ ("functionName", functionName)
+ ("shr32", shiftRightFunction(32, false))
+ ("shr64", shiftRightFunction(64, false))
+ .render();
+ });
+}
+
string ABIFunctions::abiEncodingFunction(
Type const& _from,
Type const& _to,
@@ -483,7 +577,7 @@ string ABIFunctions::abiEncodingFunctionCalldataArray(
_to.identifier() +
(_encodeAsLibraryTypes ? "_library" : "");
return createFunction(functionName, [&]() {
- solUnimplementedAssert(fromArrayType.isByteArray(), "");
+ solUnimplementedAssert(fromArrayType.isByteArray(), "Only byte arrays can be encoded from calldata currently.");
// TODO if this is not a byte array, we might just copy byte-by-byte anyway,
// because the encoding is position-independent, but we have to check that.
Whiskers templ(R"(
@@ -754,7 +848,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
_to.identifier() +
(_encodeAsLibraryTypes ? "_library" : "");
- solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "");
+ solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "Encoding struct from calldata is not yet supported.");
solAssert(&_from.structDefinition() == &_to.structDefinition(), "");
return createFunction(functionName, [&]() {
@@ -963,6 +1057,307 @@ string ABIFunctions::abiEncodingFunctionFunctionType(
});
}
+string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bool _forUseOnStack)
+{
+ // The decoding function has to perform bounds checks unless it decodes a value type.
+ // Conversely, bounds checks have to be performed before the decoding function
+ // of a value type is called.
+
+ TypePointer decodingType = _type.decodingType();
+ solAssert(decodingType, "");
+
+ if (auto arrayType = dynamic_cast<ArrayType const*>(decodingType.get()))
+ {
+ if (arrayType->dataStoredIn(DataLocation::CallData))
+ {
+ solAssert(!_fromMemory, "");
+ return abiDecodingFunctionCalldataArray(*arrayType);
+ }
+ else if (arrayType->isByteArray())
+ return abiDecodingFunctionByteArray(*arrayType, _fromMemory);
+ else
+ return abiDecodingFunctionArray(*arrayType, _fromMemory);
+ }
+ else if (auto const* structType = dynamic_cast<StructType const*>(decodingType.get()))
+ return abiDecodingFunctionStruct(*structType, _fromMemory);
+ else if (auto const* functionType = dynamic_cast<FunctionType const*>(decodingType.get()))
+ return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack);
+ else
+ return abiDecodingFunctionValueType(_type, _fromMemory);
+}
+
+string ABIFunctions::abiDecodingFunctionValueType(const Type& _type, bool _fromMemory)
+{
+ TypePointer decodingType = _type.decodingType();
+ solAssert(decodingType, "");
+ solAssert(decodingType->sizeOnStack() == 1, "");
+ solAssert(decodingType->isValueType(), "");
+ solAssert(decodingType->calldataEncodedSize() == 32, "");
+ solAssert(!decodingType->isDynamicallyEncoded(), "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "");
+ return createFunction(functionName, [&]() {
+ Whiskers templ(R"(
+ function <functionName>(offset, end) -> value {
+ value := <cleanup>(<load>(offset))
+ }
+ )");
+ templ("functionName", functionName);
+ templ("load", _fromMemory ? "mload" : "calldataload");
+ // Cleanup itself should use the type and not decodingType, because e.g.
+ // the decoding type of an enum is a plain int.
+ templ("cleanup", cleanupFunction(_type, true));
+ return templ.render();
+ });
+
+}
+
+string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory)
+{
+ solAssert(_type.dataStoredIn(DataLocation::Memory), "");
+ solAssert(!_type.isByteArray(), "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "");
+
+ solAssert(!_type.dataStoredIn(DataLocation::Storage), "");
+
+ return createFunction(functionName, [&]() {
+ string load = _fromMemory ? "mload" : "calldataload";
+ bool dynamicBase = _type.baseType()->isDynamicallyEncoded();
+ Whiskers templ(
+ R"(
+ // <readableTypeName>
+ function <functionName>(offset, end) -> array {
+ if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
+ let length := <retrieveLength>
+ array := <allocate>(<allocationSize>(length))
+ let dst := array
+ <storeLength> // might update offset and dst
+ let src := offset
+ <staticBoundsCheck>
+ for { let i := 0 } lt(i, length) { i := add(i, 1) }
+ {
+ let elementPos := <retrieveElementPos>
+ mstore(dst, <decodingFun>(elementPos, end))
+ dst := add(dst, 0x20)
+ src := add(src, <baseEncodedSize>)
+ }
+ }
+ )"
+ );
+ templ("functionName", functionName);
+ templ("readableTypeName", _type.toString(true));
+ templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)");
+ templ("allocate", allocationFunction());
+ templ("allocationSize", arrayAllocationSizeFunction(_type));
+ if (_type.isDynamicallySized())
+ templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)");
+ else
+ templ("storeLength", "");
+ if (dynamicBase)
+ {
+ templ("staticBoundsCheck", "");
+ templ("retrieveElementPos", "add(offset, " + load + "(src))");
+ templ("baseEncodedSize", "0x20");
+ }
+ else
+ {
+ string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize());
+ templ("staticBoundsCheck", "if gt(add(src, mul(length, " + baseEncodedSize + ")), end) { revert(0, 0) }");
+ templ("retrieveElementPos", "src");
+ templ("baseEncodedSize", baseEncodedSize);
+ }
+ templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false));
+ return templ.render();
+ });
+}
+
+string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
+{
+ // This does not work with arrays of complex types - the array access
+ // is not yet implemented in Solidity.
+ solAssert(_type.dataStoredIn(DataLocation::CallData), "");
+ if (!_type.isDynamicallySized())
+ solAssert(_type.length() < u256("0xffffffffffffffff"), "");
+ solAssert(!_type.baseType()->isDynamicallyEncoded(), "");
+ solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier();
+ return createFunction(functionName, [&]() {
+ string templ;
+ if (_type.isDynamicallySized())
+ templ = R"(
+ // <readableTypeName>
+ function <functionName>(offset, end) -> arrayPos, length {
+ if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
+ length := calldataload(offset)
+ if gt(length, 0xffffffffffffffff) { revert(0, 0) }
+ arrayPos := add(offset, 0x20)
+ if gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) { revert(0, 0) }
+ }
+ )";
+ else
+ templ = R"(
+ // <readableTypeName>
+ function <functionName>(offset, end) -> arrayPos {
+ arrayPos := offset
+ if gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) { revert(0, 0) }
+ }
+ )";
+ Whiskers w{templ};
+ w("functionName", functionName);
+ w("readableTypeName", _type.toString(true));
+ w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize()));
+ w("length", _type.isDynamicallyEncoded() ? "length" : toCompactHexWithPrefix(_type.length()));
+ return w.render();
+ });
+}
+
+string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory)
+{
+ solAssert(_type.dataStoredIn(DataLocation::Memory), "");
+ solAssert(_type.isByteArray(), "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "");
+
+ return createFunction(functionName, [&]() {
+ Whiskers templ(
+ R"(
+ function <functionName>(offset, end) -> array {
+ if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
+ let length := <load>(offset)
+ array := <allocate>(<allocationSize>(length))
+ mstore(array, length)
+ let src := add(offset, 0x20)
+ let dst := add(array, 0x20)
+ if gt(add(src, length), end) { revert(0, 0) }
+ <copyToMemFun>(src, dst, length)
+ }
+ )"
+ );
+ templ("functionName", functionName);
+ templ("load", _fromMemory ? "mload" : "calldataload");
+ templ("allocate", allocationFunction());
+ templ("allocationSize", arrayAllocationSizeFunction(_type));
+ templ("copyToMemFun", copyToMemoryFunction(!_fromMemory));
+ return templ.render();
+ });
+}
+
+string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory)
+{
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "");
+
+ solUnimplementedAssert(!_type.dataStoredIn(DataLocation::CallData), "");
+
+ return createFunction(functionName, [&]() {
+ Whiskers templ(R"(
+ // <readableTypeName>
+ function <functionName>(headStart, end) -> value {
+ if slt(sub(end, headStart), <minimumSize>) { revert(0, 0) }
+ value := <allocate>(<memorySize>)
+ <#members>
+ {
+ // <memberName>
+ <decode>
+ }
+ </members>
+ }
+ )");
+ templ("functionName", functionName);
+ templ("readableTypeName", _type.toString(true));
+ templ("allocate", allocationFunction());
+ solAssert(_type.memorySize() < u256("0xffffffffffffffff"), "");
+ templ("memorySize", toCompactHexWithPrefix(_type.memorySize()));
+ size_t headPos = 0;
+ vector<map<string, string>> members;
+ for (auto const& member: _type.members(nullptr))
+ {
+ solAssert(member.type, "");
+ solAssert(member.type->canLiveOutsideStorage(), "");
+ auto decodingType = member.type->decodingType();
+ solAssert(decodingType, "");
+ bool dynamic = decodingType->isDynamicallyEncoded();
+ Whiskers memberTempl(
+ dynamic ?
+ R"(
+ let offset := <load>(add(headStart, <pos>))
+ if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
+ mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
+ )" :
+ R"(
+ let offset := <pos>
+ mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
+ )"
+ );
+ memberTempl("load", _fromMemory ? "mload" : "calldataload");
+ memberTempl("pos", to_string(headPos));
+ memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name)));
+ memberTempl("abiDecode", abiDecodingFunction(*member.type, _fromMemory, false));
+
+ members.push_back({});
+ members.back()["decode"] = memberTempl.render();
+ members.back()["memberName"] = member.name;
+ headPos += dynamic ? 0x20 : decodingType->calldataEncodedSize();
+ }
+ templ("members", members);
+ templ("minimumSize", toCompactHexWithPrefix(headPos));
+ return templ.render();
+ });
+}
+
+string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack)
+{
+ solAssert(_type.kind() == FunctionType::Kind::External, "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "") +
+ (_forUseOnStack ? "_onStack" : "");
+
+ return createFunction(functionName, [&]() {
+ if (_forUseOnStack)
+ {
+ return Whiskers(R"(
+ function <functionName>(offset, end) -> addr, function_selector {
+ addr, function_selector := <splitExtFun>(<load>(offset))
+ }
+ )")
+ ("functionName", functionName)
+ ("load", _fromMemory ? "mload" : "calldataload")
+ ("splitExtFun", splitExternalFunctionIdFunction())
+ .render();
+ }
+ else
+ {
+ return Whiskers(R"(
+ function <functionName>(offset, end) -> fun {
+ fun := <cleanExtFun>(<load>(offset))
+ }
+ )")
+ ("functionName", functionName)
+ ("load", _fromMemory ? "mload" : "calldataload")
+ ("cleanExtFun", cleanupCombinedExternalFunctionIdFunction())
+ .render();
+ }
+ });
+}
+
string ABIFunctions::copyToMemoryFunction(bool _fromCalldata)
{
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
@@ -988,8 +1383,8 @@ string ABIFunctions::copyToMemoryFunction(bool _fromCalldata)
{
mstore(add(dst, i), mload(add(src, i)))
}
- switch eq(i, length)
- case 0 {
+ if gt(i, length)
+ {
// clear end
mstore(add(dst, length), 0)
}
@@ -1098,6 +1493,33 @@ string ABIFunctions::arrayLengthFunction(ArrayType const& _type)
});
}
+string ABIFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
+{
+ solAssert(_type.dataStoredIn(DataLocation::Memory), "");
+ string functionName = "array_allocation_size_" + _type.identifier();
+ return createFunction(functionName, [&]() {
+ Whiskers w(R"(
+ function <functionName>(length) -> size {
+ // Make sure we can allocate memory without overflow
+ if gt(length, 0xffffffffffffffff) { revert(0, 0) }
+ size := <allocationSize>
+ <addLengthSlot>
+ }
+ )");
+ w("functionName", functionName);
+ if (_type.isByteArray())
+ // Round up
+ w("allocationSize", "and(add(length, 0x1f), not(0x1f))");
+ else
+ w("allocationSize", "mul(length, 0x20)");
+ if (_type.isDynamicallySized())
+ w("addLengthSlot", "size := add(size, 0x20)");
+ else
+ w("addLengthSlot", "");
+ return w.render();
+ });
+}
+
string ABIFunctions::arrayDataAreaFunction(ArrayType const& _type)
{
string functionName = "array_dataslot_" + _type.identifier();
@@ -1189,6 +1611,25 @@ string ABIFunctions::nextArrayElementFunction(ArrayType const& _type)
});
}
+string ABIFunctions::allocationFunction()
+{
+ string functionName = "allocateMemory";
+ return createFunction(functionName, [&]() {
+ return Whiskers(R"(
+ function <functionName>(size) -> memPtr {
+ memPtr := mload(<freeMemoryPointer>)
+ let newFreePtr := add(memPtr, size)
+ // protect against overflow
+ if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }
+ mstore(<freeMemoryPointer>, newFreePtr)
+ }
+ )")
+ ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
+ ("functionName", functionName)
+ .render();
+ });
+}
+
string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator)
{
if (!m_requestedFunctions.count(_name))
diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h
index e61f68bc..2b582e84 100644
--- a/libsolidity/codegen/ABIFunctions.h
+++ b/libsolidity/codegen/ABIFunctions.h
@@ -66,6 +66,16 @@ public:
bool _encodeAsLibraryTypes = false
);
+ /// @returns name of an assembly function to ABI-decode values of @a _types
+ /// into memory. If @a _fromMemory is true, decodes from memory instead of
+ /// from calldata.
+ /// Can allocate memory.
+ /// Inputs: <source_offset> <source_end> (layout reversed on stack)
+ /// Outputs: <value0> <value1> ... <valuen>
+ /// The values represent stack slots. If a type occupies more or less than one
+ /// stack slot, it takes exactly that number of values.
+ std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false);
+
/// @returns concatenation of all generated functions.
std::string requestedFunctions();
@@ -87,6 +97,10 @@ private:
/// for use in the ABI.
std::string combineExternalFunctionIdFunction();
+ /// @returns a function that splits the address and selector from a single value
+ /// for use in the ABI.
+ std::string splitExternalFunctionIdFunction();
+
/// @returns the name of the ABI encoding function with the given type
/// and queues the generation of the function to the requested functions.
/// @param _fromStack if false, the input value was just loaded from storage
@@ -146,6 +160,31 @@ private:
bool _fromStack
);
+ /// @returns the name of the ABI decoding function for the given type
+ /// and queues the generation of the function to the requested functions.
+ /// The caller has to ensure that no out of bounds access (at least to the static
+ /// part) can happen inside this function.
+ /// @param _fromMemory if decoding from memory instead of from calldata
+ /// @param _forUseOnStack if the decoded value is stored on stack or in memory.
+ std::string abiDecodingFunction(
+ Type const& _Type,
+ bool _fromMemory,
+ bool _forUseOnStack
+ );
+
+ /// Part of @a abiDecodingFunction for value types.
+ std::string abiDecodingFunctionValueType(Type const& _type, bool _fromMemory);
+ /// Part of @a abiDecodingFunction for "regular" array types.
+ std::string abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory);
+ /// Part of @a abiDecodingFunction for calldata array types.
+ std::string abiDecodingFunctionCalldataArray(ArrayType const& _type);
+ /// Part of @a abiDecodingFunction for byte array types.
+ std::string abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory);
+ /// Part of @a abiDecodingFunction for struct types.
+ std::string abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory);
+ /// Part of @a abiDecodingFunction for array types.
+ std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack);
+
/// @returns a function that copies raw bytes of dynamic length from calldata
/// or memory to memory.
/// Pads with zeros and might write more than exactly length.
@@ -158,6 +197,10 @@ private:
std::string roundUpFunction();
std::string arrayLengthFunction(ArrayType const& _type);
+ /// @returns the name of a function that computes the number of bytes required
+ /// to store an array in memory given its length (internally encoded, not ABI encoded).
+ /// The function reverts for too large lengthes.
+ std::string arrayAllocationSizeFunction(ArrayType const& _type);
/// @returns the name of a function that converts a storage slot number
/// or a memory pointer to the slot number / memory pointer for the data position of an array
/// which is stored in that slot / memory area.
@@ -166,6 +209,12 @@ private:
/// Only works for memory arrays and storage arrays that store one item per slot.
std::string nextArrayElementFunction(ArrayType const& _type);
+ /// @returns the name of a function that allocates memory.
+ /// Modifies the "free memory pointer"
+ /// Arguments: size
+ /// Return value: pointer
+ std::string allocationFunction();
+
/// Helper function that uses @a _creator to create a function and add it to
/// @a m_requestedFunctions if it has not been created yet and returns @a _name in both
/// cases.
diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp
index 44264a07..d3afada5 100644
--- a/libsolidity/codegen/Compiler.cpp
+++ b/libsolidity/codegen/Compiler.cpp
@@ -51,6 +51,7 @@ void Compiler::compileClone(
map<ContractDefinition const*, eth::Assembly const*> const& _contracts
)
{
+ solAssert(!_contract.isLibrary(), "");
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize);
ContractCompiler cloneCompiler(&runtimeCompiler, m_context, m_optimize);
m_runtimeSub = cloneCompiler.compileClone(_contract, _contracts);
diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp
index ce9c3b7f..7a88475a 100644
--- a/libsolidity/codegen/CompilerContext.cpp
+++ b/libsolidity/codegen/CompilerContext.cpp
@@ -319,14 +319,19 @@ void CompilerContext::appendInlineAssembly(
ErrorList errors;
ErrorReporter errorReporter(errors);
auto scanner = make_shared<Scanner>(CharStream(_assembly), "--CODEGEN--");
- auto parserResult = assembly::Parser(errorReporter).parse(scanner);
+ auto parserResult = assembly::Parser(errorReporter, assembly::AsmFlavour::Strict).parse(scanner);
#ifdef SOL_OUTPUT_ASM
cout << assembly::AsmPrinter()(*parserResult) << endl;
#endif
assembly::AsmAnalysisInfo analysisInfo;
bool analyzerResult = false;
if (parserResult)
- analyzerResult = assembly::AsmAnalyzer(analysisInfo, errorReporter, false, identifierAccess.resolve).analyze(*parserResult);
+ analyzerResult = assembly::AsmAnalyzer(
+ analysisInfo,
+ errorReporter,
+ assembly::AsmFlavour::Strict,
+ identifierAccess.resolve
+ ).analyze(*parserResult);
if (!parserResult || !errorReporter.errors().empty() || !analyzerResult)
{
string message =
@@ -347,6 +352,9 @@ void CompilerContext::appendInlineAssembly(
solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block.");
assembly::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, identifierAccess, _system);
+
+ // Reset the source location to the one of the node (instead of the CODEGEN source location)
+ updateSourceLocation();
}
FunctionDefinition const& CompilerContext::resolveVirtualFunction(
diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h
index 7743fd3f..a155a3a5 100644
--- a/libsolidity/codegen/CompilerContext.h
+++ b/libsolidity/codegen/CompilerContext.h
@@ -174,6 +174,9 @@ public:
eth::AssemblyItem appendData(bytes const& _data) { return m_asm->append(_data); }
/// Appends the address (virtual, will be filled in by linker) of a library.
void appendLibraryAddress(std::string const& _identifier) { m_asm->appendLibraryAddress(_identifier); }
+ /// Appends a zero-address that can be replaced by something else at deploy time (if the
+ /// position in bytecode is known).
+ void appendDeployTimeAddress() { m_asm->append(eth::PushDeployTimeAddress); }
/// Resets the stack of visited nodes with a new stack having only @c _node
void resetVisitedNodes(ASTNode const* _node);
/// Pops the stack of visited nodes
@@ -187,8 +190,8 @@ public:
CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; }
CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; }
- /// Appends inline assembly. @a _replacements are string-matching replacements that are performed
- /// prior to parsing the inline assembly.
+ /// Appends inline assembly (strict mode).
+ /// @a _replacements are string-matching replacements that are performed prior to parsing the inline assembly.
/// @param _localVariables assigns stack positions to variables with the last one being the stack top
/// @param _system if true, this is a "system-level" assembly where all functions use named labels.
void appendInlineAssembly(
diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp
index f9b181ae..533aca5c 100644
--- a/libsolidity/codegen/CompilerUtils.cpp
+++ b/libsolidity/codegen/CompilerUtils.cpp
@@ -121,7 +121,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
{
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
{
- solUnimplementedAssert(ref->location() == DataLocation::Memory, "");
+ solUnimplementedAssert(ref->location() == DataLocation::Memory, "Only in-memory reference type can be stored.");
storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries);
}
else if (auto str = dynamic_cast<StringLiteralType const*>(&_type))
@@ -319,6 +319,23 @@ void CompilerUtils::abiEncodeV2(
m_context << ret.tag();
}
+void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory)
+{
+ // stack: <source_offset>
+ auto ret = m_context.pushNewTag();
+ m_context << Instruction::SWAP1;
+ if (_fromMemory)
+ // TODO pass correct size for the memory case
+ m_context << (u256(1) << 63);
+ else
+ m_context << Instruction::CALLDATASIZE;
+ m_context << Instruction::SWAP1;
+ string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory);
+ m_context.appendJumpTo(m_context.namedTag(decoderName));
+ m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3);
+ m_context << ret.tag();
+}
+
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
{
auto repeat = m_context.newTag();
diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h
index ad3989ad..3cde281b 100644
--- a/libsolidity/codegen/CompilerUtils.h
+++ b/libsolidity/codegen/CompilerUtils.h
@@ -146,6 +146,13 @@ public:
bool _encodeAsLibraryTypes = false
);
+ /// Decodes data from ABI encoding into internal encoding. If @a _fromMemory is set to true,
+ /// the data is taken from memory instead of from calldata.
+ /// Can allocate memory.
+ /// Stack pre: <source_offset>
+ /// Stack post: <value0> <value1> ... <valuen>
+ void abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory = false);
+
/// Zero-initialises (the data part of) an already allocated memory array.
/// Length has to be nonzero!
/// Stack pre: <length> <memptr>
diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp
index 74565ae4..f463db94 100644
--- a/libsolidity/codegen/ContractCompiler.cpp
+++ b/libsolidity/codegen/ContractCompiler.cpp
@@ -64,6 +64,12 @@ void ContractCompiler::compileContract(
)
{
CompilerContext::LocationSetter locationSetter(m_context, _contract);
+
+ if (_contract.isLibrary())
+ // Check whether this is a call (true) or a delegatecall (false).
+ // This has to be the first code in the contract.
+ appendDelegatecallCheck();
+
initializeContext(_contract, _contracts);
appendFunctionSelector(_contract);
appendMissingFunctions();
@@ -75,8 +81,13 @@ size_t ContractCompiler::compileConstructor(
)
{
CompilerContext::LocationSetter locationSetter(m_context, _contract);
- initializeContext(_contract, _contracts);
- return packIntoContractCreator(_contract);
+ if (_contract.isLibrary())
+ return deployLibrary(_contract);
+ else
+ {
+ initializeContext(_contract, _contracts);
+ return packIntoContractCreator(_contract);
+ }
}
size_t ContractCompiler::compileClone(
@@ -122,6 +133,7 @@ void ContractCompiler::appendCallValueCheck()
void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract)
{
+ solAssert(!_contract.isLibrary(), "Tried to initialize library.");
CompilerContext::LocationSetter locationSetter(m_context, _contract);
// Determine the arguments that are used for the base constructors.
std::vector<ContractDefinition const*> const& bases = _contract.annotation().linearizedBaseContracts;
@@ -163,6 +175,7 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c
size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract)
{
solAssert(!!m_runtimeCompiler, "");
+ solAssert(!_contract.isLibrary(), "Tried to use contract creator or library.");
appendInitAndConstructorCode(_contract);
@@ -188,6 +201,34 @@ size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _cont
return m_context.runtimeSub();
}
+size_t ContractCompiler::deployLibrary(ContractDefinition const& _contract)
+{
+ solAssert(!!m_runtimeCompiler, "");
+ solAssert(_contract.isLibrary(), "Tried to deploy contract as library.");
+
+ CompilerContext::LocationSetter locationSetter(m_context, _contract);
+
+ solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered");
+ m_context.pushSubroutineSize(m_context.runtimeSub());
+ m_context.pushSubroutineOffset(m_context.runtimeSub());
+ m_context.appendInlineAssembly(R"(
+ {
+ // If code starts at 11, an mstore(0) writes to the full PUSH20 plus data
+ // without the need for a shift.
+ let codepos := 11
+ codecopy(codepos, subOffset, subSize)
+ // Check that the first opcode is a PUSH20
+ switch eq(0x73, byte(0, mload(codepos)))
+ case 0 { invalid() }
+ mstore(0, address())
+ mstore8(codepos, 0x73)
+ return(codepos, subSize)
+ }
+ )", {"subSize", "subOffset"});
+
+ return m_context.runtimeSub();
+}
+
void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _constructor)
{
CompilerContext::LocationSetter locationSetter(m_context, _constructor);
@@ -244,11 +285,26 @@ void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor)
_constructor.accept(*this);
}
+void ContractCompiler::appendDelegatecallCheck()
+{
+ // Special constant that will be replaced by the address at deploy time.
+ // At compilation time, this is just "PUSH20 00...000".
+ m_context.appendDeployTimeAddress();
+ m_context << Instruction::ADDRESS << Instruction::EQ;
+ // The result on the stack is
+ // "We have not been called via DELEGATECALL".
+}
+
void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contract)
{
map<FixedHash<4>, FunctionTypePointer> interfaceFunctions = _contract.interfaceFunctions();
map<FixedHash<4>, const eth::AssemblyItem> callDataUnpackerEntryPoints;
+ if (_contract.isLibrary())
+ {
+ solAssert(m_context.stackHeight() == 1, "CALL / DELEGATECALL flag expected.");
+ }
+
FunctionDefinition const* fallback = _contract.fallbackFunction();
eth::AssemblyItem notFound = m_context.newTag();
// directly jump to fallback if the data is too short to contain a function selector
@@ -260,7 +316,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
if (!interfaceFunctions.empty())
CompilerUtils(m_context).loadFromMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8), true);
- // stack now is: 1 0 <funhash>
+ // stack now is: <can-call-non-view-functions>? <funhash>
for (auto const& it: interfaceFunctions)
{
callDataUnpackerEntryPoints.insert(std::make_pair(it.first, m_context.newTag()));
@@ -272,6 +328,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
m_context << notFound;
if (fallback)
{
+ solAssert(!_contract.isLibrary(), "");
if (!fallback->isPayable())
appendCallValueCheck();
@@ -291,6 +348,13 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
CompilerContext::LocationSetter locationSetter(m_context, functionType->declaration());
m_context << callDataUnpackerEntryPoints.at(it.first);
+ if (_contract.isLibrary() && functionType->stateMutability() > StateMutability::View)
+ {
+ // If the function is not a view function and is called without DELEGATECALL,
+ // we revert.
+ m_context << dupInstruction(2);
+ m_context.appendConditionalRevert();
+ }
m_context.setStackOffset(0);
// We have to allow this for libraries, because value of the previous
// call is still visible in the delegatecall.
@@ -322,6 +386,15 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter
{
// We do not check the calldata size, everything is zero-padded
+ if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
+ {
+ // Use the new JULIA-based decoding function
+ auto stackHeightBefore = m_context.stackHeight();
+ CompilerUtils(m_context).abiDecodeV2(_typeParameters, _fromMemory);
+ solAssert(m_context.stackHeight() - stackHeightBefore == CompilerUtils(m_context).sizeOnStack(_typeParameters) - 1, "");
+ return;
+ }
+
//@todo this does not yet support nested dynamic arrays
// Retain the offset pointer as base_offset, the point from which the data offsets are computed.
@@ -432,6 +505,7 @@ void ContractCompiler::registerStateVariables(ContractDefinition const& _contrac
void ContractCompiler::initializeStateVariables(ContractDefinition const& _contract)
{
+ solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library.");
for (VariableDeclaration const* variable: _contract.stateVariables())
if (variable->value() && !variable->isConstant())
ExpressionCompiler(m_context, m_optimise).appendStateVariableInitialization(*variable);
diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h
index 7c5ee59f..1fd80d05 100644
--- a/libsolidity/codegen/ContractCompiler.h
+++ b/libsolidity/codegen/ContractCompiler.h
@@ -75,10 +75,19 @@ private:
/// with a new and initialized context. Adds the constructor code.
/// @returns the identifier of the runtime sub assembly
size_t packIntoContractCreator(ContractDefinition const& _contract);
+ /// Appends code that deploys the given contract as a library.
+ /// Will also add code that modifies the contract in memory by injecting the current address
+ /// for the call protector.
+ size_t deployLibrary(ContractDefinition const& _contract);
/// Appends state variable initialisation and constructor code.
void appendInitAndConstructorCode(ContractDefinition const& _contract);
void appendBaseConstructor(FunctionDefinition const& _constructor);
void appendConstructor(FunctionDefinition const& _constructor);
+ /// Appends code that returns a boolean flag on the stack that tells whether
+ /// the contract has been called via delegatecall (false) or regular call (true).
+ /// This is done by inserting a specific push constant as the first instruction
+ /// whose data will be modified in memory at deploy time.
+ void appendDelegatecallCheck();
void appendFunctionSelector(ContractDefinition const& _contract);
void appendCallValueCheck();
/// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers.
diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp
index bb8c4a94..61920592 100644
--- a/libsolidity/codegen/ExpressionCompiler.cpp
+++ b/libsolidity/codegen/ExpressionCompiler.cpp
@@ -765,7 +765,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
case FunctionType::Kind::AddMod:
case FunctionType::Kind::MulMod:
{
- for (unsigned i = 0; i < 3; i ++)
+ arguments[2]->accept(*this);
+ utils().convertType(*arguments[2]->annotation().type, IntegerType(256));
+ m_context << Instruction::DUP1 << Instruction::ISZERO;
+ m_context.appendConditionalInvalid();
+ for (unsigned i = 1; i < 3; i ++)
{
arguments[2 - i]->accept(*this);
utils().convertType(*arguments[2 - i]->annotation().type, IntegerType(256));
@@ -1014,6 +1018,30 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
_memberAccess.expression().accept(*this);
return false;
}
+ // Another special case for `this.f.selector` which does not need the address.
+ // There are other uses of `.selector` which do need the address, but we want this
+ // specific use to be a pure expression.
+ if (
+ _memberAccess.expression().annotation().type->category() == Type::Category::Function &&
+ member == "selector"
+ )
+ if (auto const* expr = dynamic_cast<MemberAccess const*>(&_memberAccess.expression()))
+ if (auto const* exprInt = dynamic_cast<Identifier const*>(&expr->expression()))
+ if (exprInt->name() == "this")
+ if (Declaration const* declaration = expr->annotation().referencedDeclaration)
+ {
+ u256 identifier;
+ if (auto const* variable = dynamic_cast<VariableDeclaration const*>(declaration))
+ identifier = FunctionType(*variable).externalIdentifier();
+ else if (auto const* function = dynamic_cast<FunctionDefinition const*>(declaration))
+ identifier = FunctionType(*function).externalIdentifier();
+ else
+ solAssert(false, "Contract member is neither variable nor function.");
+ m_context << identifier;
+ /// need to store store it as bytes4
+ utils().leftShiftNumberOnStack(224);
+ return false;
+ }
_memberAccess.expression().accept(*this);
switch (_memberAccess.expression().annotation().type->category())
diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp
index 2d2f05ec..a64024b3 100644
--- a/libsolidity/formal/SMTChecker.cpp
+++ b/libsolidity/formal/SMTChecker.cpp
@@ -23,9 +23,12 @@
#include <libsolidity/formal/SMTLib2Interface.h>
#endif
+#include <libsolidity/formal/VariableUsage.h>
+
#include <libsolidity/interface/ErrorReporter.h>
#include <boost/range/adaptor/map.hpp>
+#include <boost/algorithm/string/replace.hpp>
using namespace std;
using namespace dev;
@@ -44,28 +47,15 @@ SMTChecker::SMTChecker(ErrorReporter& _errorReporter, ReadCallback::Callback con
void SMTChecker::analyze(SourceUnit const& _source)
{
+ m_variableUsage = make_shared<VariableUsage>(_source);
if (_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker))
- {
- m_interface->reset();
- m_currentSequenceCounter.clear();
- m_nextFreeSequenceCounter.clear();
_source.accept(*this);
- }
}
void SMTChecker::endVisit(VariableDeclaration const& _varDecl)
{
- if (_varDecl.value())
- {
- m_errorReporter.warning(
- _varDecl.location(),
- "Assertion checker does not yet support this."
- );
- }
- else if (_varDecl.isLocalOrReturn())
- createVariable(_varDecl, true);
- else if (_varDecl.isCallableParameter())
- createVariable(_varDecl, false);
+ if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value())
+ assignment(_varDecl, *_varDecl.value(), _varDecl.location());
}
bool SMTChecker::visit(FunctionDefinition const& _function)
@@ -75,20 +65,23 @@ bool SMTChecker::visit(FunctionDefinition const& _function)
_function.location(),
"Assertion checker does not yet support constructors and functions with modifiers."
);
- // TODO actually we probably also have to reset all local variables and similar things.
m_currentFunction = &_function;
- m_interface->push();
+ // We only handle local variables, so we clear at the beginning of the function.
+ // If we add storage variables, those should be cleared differently.
+ m_interface->reset();
+ m_currentSequenceCounter.clear();
+ m_nextFreeSequenceCounter.clear();
+ m_pathConditions.clear();
+ m_conditionalExecutionHappened = false;
+ initializeLocalVariables(_function);
return true;
}
void SMTChecker::endVisit(FunctionDefinition const&)
{
// TOOD we could check for "reachability", i.e. satisfiability here.
- // We only handle local variables, so we clear everything.
+ // We only handle local variables, so we clear at the beginning of the function.
// If we add storage variables, those should be cleared differently.
- m_currentSequenceCounter.clear();
- m_nextFreeSequenceCounter.clear();
- m_interface->pop();
m_currentFunction = nullptr;
}
@@ -96,57 +89,85 @@ bool SMTChecker::visit(IfStatement const& _node)
{
_node.condition().accept(*this);
- // TODO Check if condition is always true
-
- auto countersAtStart = m_currentSequenceCounter;
- m_interface->push();
- m_interface->addAssertion(expr(_node.condition()));
- _node.trueStatement().accept(*this);
- auto countersAtEndOfTrue = m_currentSequenceCounter;
- m_interface->pop();
+ checkBooleanNotConstant(_node.condition(), "Condition is always $VALUE.");
- decltype(m_currentSequenceCounter) countersAtEndOfFalse;
+ auto countersEndFalse = m_currentSequenceCounter;
+ auto countersEndTrue = visitBranch(_node.trueStatement(), expr(_node.condition()));
+ vector<Declaration const*> touchedVariables = m_variableUsage->touchedVariables(_node.trueStatement());
if (_node.falseStatement())
{
- m_currentSequenceCounter = countersAtStart;
- m_interface->push();
- m_interface->addAssertion(!expr(_node.condition()));
- _node.falseStatement()->accept(*this);
- countersAtEndOfFalse = m_currentSequenceCounter;
- m_interface->pop();
+ countersEndFalse = visitBranch(*_node.falseStatement(), !expr(_node.condition()));
+ touchedVariables += m_variableUsage->touchedVariables(*_node.falseStatement());
}
- else
- countersAtEndOfFalse = countersAtStart;
- // Reset all values that have been touched.
+ mergeVariables(touchedVariables, expr(_node.condition()), countersEndTrue, countersEndFalse);
- // TODO this should use a previously generated side-effect structure
+ return false;
+}
- solAssert(countersAtEndOfFalse.size() == countersAtEndOfTrue.size(), "");
- for (auto const& declCounter: countersAtEndOfTrue)
+bool SMTChecker::visit(WhileStatement const& _node)
+{
+ auto touchedVariables = m_variableUsage->touchedVariables(_node);
+ resetVariables(touchedVariables);
+ if (_node.isDoWhile())
{
- solAssert(countersAtEndOfFalse.count(declCounter.first), "");
- auto decl = declCounter.first;
- int trueCounter = countersAtEndOfTrue.at(decl);
- int falseCounter = countersAtEndOfFalse.at(decl);
- if (trueCounter == falseCounter)
- continue; // Was not modified
- newValue(*decl);
- setValue(*decl, 0);
+ visitBranch(_node.body());
+ // TODO the assertions generated in the body should still be active in the condition
+ _node.condition().accept(*this);
+ checkBooleanNotConstant(_node.condition(), "Do-while loop condition is always $VALUE.");
}
+ else
+ {
+ _node.condition().accept(*this);
+ checkBooleanNotConstant(_node.condition(), "While loop condition is always $VALUE.");
+
+ visitBranch(_node.body(), expr(_node.condition()));
+ }
+ resetVariables(touchedVariables);
+
return false;
}
-bool SMTChecker::visit(WhileStatement const& _node)
+bool SMTChecker::visit(ForStatement const& _node)
{
- _node.condition().accept(*this);
+ if (_node.initializationExpression())
+ _node.initializationExpression()->accept(*this);
+
+ // Do not reset the init expression part.
+ auto touchedVariables =
+ m_variableUsage->touchedVariables(_node.body());
+ if (_node.condition())
+ touchedVariables += m_variableUsage->touchedVariables(*_node.condition());
+ if (_node.loopExpression())
+ touchedVariables += m_variableUsage->touchedVariables(*_node.loopExpression());
+ // Remove duplicates
+ std::sort(touchedVariables.begin(), touchedVariables.end());
+ touchedVariables.erase(std::unique(touchedVariables.begin(), touchedVariables.end()), touchedVariables.end());
+
+ resetVariables(touchedVariables);
+
+ if (_node.condition())
+ {
+ _node.condition()->accept(*this);
+ checkBooleanNotConstant(*_node.condition(), "For loop condition is always $VALUE.");
+ }
- //m_interface->push();
- //m_interface->addAssertion(expr(_node.condition()));
- // TDOO clear knowledge (increment sequence numbers and add bounds assertions ) apart from assertions
+ VariableSequenceCounters sequenceCountersStart = m_currentSequenceCounter;
+ m_interface->push();
+ if (_node.condition())
+ m_interface->addAssertion(expr(*_node.condition()));
+ _node.body().accept(*this);
+ if (_node.loopExpression())
+ _node.loopExpression()->accept(*this);
- // TODO combine similar to if
- return true;
+ m_interface->pop();
+
+ m_conditionalExecutionHappened = true;
+ m_currentSequenceCounter = sequenceCountersStart;
+
+ resetVariables(touchedVariables);
+
+ return false;
}
void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl)
@@ -159,9 +180,7 @@ void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl)
else if (knownVariable(*_varDecl.declarations()[0]))
{
if (_varDecl.initialValue())
- // TODO more checks?
- // TODO add restrictions about type (might be assignment from smaller type)
- m_interface->addAssertion(newValue(*_varDecl.declarations()[0]) == expr(*_varDecl.initialValue()));
+ assignment(*_varDecl.declarations()[0], *_varDecl.initialValue(), _varDecl.location());
}
else
m_errorReporter.warning(
@@ -190,9 +209,10 @@ void SMTChecker::endVisit(Assignment const& _assignment)
{
Declaration const* decl = identifier->annotation().referencedDeclaration;
if (knownVariable(*decl))
- // TODO more checks?
- // TODO add restrictions about type (might be assignment from smaller type)
- m_interface->addAssertion(newValue(*decl) == expr(_assignment.rightHandSide()));
+ {
+ assignment(*decl, _assignment.rightHandSide(), _assignment.location());
+ defineExpr(_assignment, expr(_assignment.rightHandSide()));
+ }
else
m_errorReporter.warning(
_assignment.location(),
@@ -214,7 +234,81 @@ void SMTChecker::endVisit(TupleExpression const& _tuple)
"Assertion checker does not yet implement tules and inline arrays."
);
else
- m_interface->addAssertion(expr(_tuple) == expr(*_tuple.components()[0]));
+ defineExpr(_tuple, expr(*_tuple.components()[0]));
+}
+
+void SMTChecker::checkUnderOverflow(smt::Expression _value, IntegerType const& _type, SourceLocation const& _location)
+{
+ checkCondition(
+ _value < minValue(_type),
+ _location,
+ "Underflow (resulting value less than " + formatNumber(_type.minValue()) + ")",
+ "value",
+ &_value
+ );
+ checkCondition(
+ _value > maxValue(_type),
+ _location,
+ "Overflow (resulting value larger than " + formatNumber(_type.maxValue()) + ")",
+ "value",
+ &_value
+ );
+}
+
+void SMTChecker::endVisit(UnaryOperation const& _op)
+{
+ switch (_op.getOperator())
+ {
+ case Token::Not: // !
+ {
+ solAssert(_op.annotation().type->category() == Type::Category::Bool, "");
+ defineExpr(_op, !expr(_op.subExpression()));
+ break;
+ }
+ case Token::Inc: // ++ (pre- or postfix)
+ case Token::Dec: // -- (pre- or postfix)
+ {
+ solAssert(_op.annotation().type->category() == Type::Category::Integer, "");
+ solAssert(_op.subExpression().annotation().lValueRequested, "");
+ if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_op.subExpression()))
+ {
+ Declaration const* decl = identifier->annotation().referencedDeclaration;
+ if (knownVariable(*decl))
+ {
+ auto innerValue = currentValue(*decl);
+ auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1;
+ assignment(*decl, newValue, _op.location());
+ defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
+ }
+ else
+ m_errorReporter.warning(
+ _op.location(),
+ "Assertion checker does not yet implement such assignments."
+ );
+ }
+ else
+ m_errorReporter.warning(
+ _op.location(),
+ "Assertion checker does not yet implement such increments / decrements."
+ );
+ break;
+ }
+ case Token::Add: // +
+ defineExpr(_op, expr(_op.subExpression()));
+ break;
+ case Token::Sub: // -
+ {
+ defineExpr(_op, 0 - expr(_op.subExpression()));
+ if (auto intType = dynamic_cast<IntegerType const*>(_op.annotation().type.get()))
+ checkUnderOverflow(expr(_op), *intType, _op.location());
+ break;
+ }
+ default:
+ m_errorReporter.warning(
+ _op.location(),
+ "Assertion checker does not yet implement this operator."
+ );
+ }
}
void SMTChecker::endVisit(BinaryOperation const& _op)
@@ -252,16 +346,14 @@ void SMTChecker::endVisit(FunctionCall const& _funCall)
solAssert(args.size() == 1, "");
solAssert(args[0]->annotation().type->category() == Type::Category::Bool, "");
checkCondition(!(expr(*args[0])), _funCall.location(), "Assertion violation");
- m_interface->addAssertion(expr(*args[0]));
+ addPathImpliedExpression(expr(*args[0]));
}
else if (funType.kind() == FunctionType::Kind::Require)
{
solAssert(args.size() == 1, "");
solAssert(args[0]->annotation().type->category() == Type::Category::Bool, "");
- m_interface->addAssertion(expr(*args[0]));
- checkCondition(!(expr(*args[0])), _funCall.location(), "Unreachable code");
- // TODO is there something meaningful we can check here?
- // We can check whether the condition is always fulfilled or never fulfilled.
+ checkBooleanNotConstant(*args[0], "Condition is always $VALUE.");
+ addPathImpliedExpression(expr(*args[0]));
}
}
@@ -269,23 +361,17 @@ void SMTChecker::endVisit(Identifier const& _identifier)
{
Declaration const* decl = _identifier.annotation().referencedDeclaration;
solAssert(decl, "");
- if (dynamic_cast<IntegerType const*>(_identifier.annotation().type.get()))
+ if (_identifier.annotation().lValueRequested)
{
- m_interface->addAssertion(expr(_identifier) == currentValue(*decl));
- return;
+ // Will be translated as part of the node that requested the lvalue.
}
+ else if (dynamic_cast<IntegerType const*>(_identifier.annotation().type.get()))
+ defineExpr(_identifier, currentValue(*decl));
else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get()))
{
if (fun->kind() == FunctionType::Kind::Assert || fun->kind() == FunctionType::Kind::Require)
return;
- // TODO for others, clear our knowledge about storage and memory
}
- m_errorReporter.warning(
- _identifier.location(),
- "Assertion checker does not yet support the type of this expression (" +
- _identifier.annotation().type->toString() +
- ")."
- );
}
void SMTChecker::endVisit(Literal const& _literal)
@@ -296,12 +382,14 @@ void SMTChecker::endVisit(Literal const& _literal)
if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&type))
solAssert(!rational->isFractional(), "");
- m_interface->addAssertion(expr(_literal) == smt::Expression(type.literalValue(&_literal)));
+ defineExpr(_literal, smt::Expression(type.literalValue(&_literal)));
}
+ else if (type.category() == Type::Category::Bool)
+ defineExpr(_literal, smt::Expression(_literal.token() == Token::TrueLiteral ? true : false));
else
m_errorReporter.warning(
_literal.location(),
- "Assertion checker does not yet support the type of this expression (" +
+ "Assertion checker does not yet support the type of this literal (" +
_literal.annotation().type->toString() +
")."
);
@@ -314,36 +402,30 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op)
case Token::Add:
case Token::Sub:
case Token::Mul:
+ case Token::Div:
{
solAssert(_op.annotation().commonType, "");
solAssert(_op.annotation().commonType->category() == Type::Category::Integer, "");
+ auto const& intType = dynamic_cast<IntegerType const&>(*_op.annotation().commonType);
smt::Expression left(expr(_op.leftExpression()));
smt::Expression right(expr(_op.rightExpression()));
Token::Value op = _op.getOperator();
smt::Expression value(
op == Token::Add ? left + right :
op == Token::Sub ? left - right :
+ op == Token::Div ? division(left, right, intType) :
/*op == Token::Mul*/ left * right
);
- // Overflow check
- auto const& intType = dynamic_cast<IntegerType const&>(*_op.annotation().commonType);
- checkCondition(
- value < minValue(intType),
- _op.location(),
- "Underflow (resulting value less than " + formatNumber(intType.minValue()) + ")",
- "value",
- &value
- );
- checkCondition(
- value > maxValue(intType),
- _op.location(),
- "Overflow (resulting value larger than " + formatNumber(intType.maxValue()) + ")",
- "value",
- &value
- );
+ if (_op.getOperator() == Token::Div)
+ {
+ checkCondition(right == 0, _op.location(), "Division by zero", "value", &right);
+ m_interface->addAssertion(right != 0);
+ }
+
+ checkUnderOverflow(value, intType, _op.location());
- m_interface->addAssertion(expr(_op) == value);
+ defineExpr(_op, value);
break;
}
default:
@@ -371,7 +453,7 @@ void SMTChecker::compareOperation(BinaryOperation const& _op)
/*op == Token::GreaterThanOrEqual*/ (left >= right)
);
// TODO: check that other values for op are not possible.
- m_interface->addAssertion(expr(_op) == value);
+ defineExpr(_op, value);
}
else
m_errorReporter.warning(
@@ -386,16 +468,63 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op)
solAssert(_op.annotation().commonType, "");
if (_op.annotation().commonType->category() == Type::Category::Bool)
{
+ // @TODO check that both of them are not constant
if (_op.getOperator() == Token::And)
- m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) && expr(_op.rightExpression()));
+ defineExpr(_op, expr(_op.leftExpression()) && expr(_op.rightExpression()));
else
- m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) || expr(_op.rightExpression()));
+ defineExpr(_op, expr(_op.leftExpression()) || expr(_op.rightExpression()));
}
else
m_errorReporter.warning(
_op.location(),
"Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for boolean operations"
- );
+ );
+}
+
+smt::Expression SMTChecker::division(smt::Expression _left, smt::Expression _right, IntegerType const& _type)
+{
+ // Signed division in SMTLIB2 rounds differently for negative division.
+ if (_type.isSigned())
+ return (smt::Expression::ite(
+ _left >= 0,
+ smt::Expression::ite(_right >= 0, _left / _right, 0 - (_left / (0 - _right))),
+ smt::Expression::ite(_right >= 0, 0 - ((0 - _left) / _right), (0 - _left) / (0 - _right))
+ ));
+ else
+ return _left / _right;
+}
+
+void SMTChecker::assignment(Declaration const& _variable, Expression const& _value, SourceLocation const& _location)
+{
+ assignment(_variable, expr(_value), _location);
+}
+
+void SMTChecker::assignment(Declaration const& _variable, smt::Expression const& _value, SourceLocation const& _location)
+{
+ TypePointer type = _variable.type();
+ if (auto const* intType = dynamic_cast<IntegerType const*>(type.get()))
+ checkUnderOverflow(_value, *intType, _location);
+ m_interface->addAssertion(newValue(_variable) == _value);
+}
+
+SMTChecker::VariableSequenceCounters SMTChecker::visitBranch(Statement const& _statement, smt::Expression _condition)
+{
+ return visitBranch(_statement, &_condition);
+}
+
+SMTChecker::VariableSequenceCounters SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition)
+{
+ VariableSequenceCounters sequenceCountersStart = m_currentSequenceCounter;
+
+ if (_condition)
+ pushPathCondition(*_condition);
+ _statement.accept(*this);
+ if (_condition)
+ popPathCondition();
+
+ m_conditionalExecutionHappened = true;
+ std::swap(sequenceCountersStart, m_currentSequenceCounter);
+ return sequenceCountersStart;
}
void SMTChecker::checkCondition(
@@ -407,7 +536,7 @@ void SMTChecker::checkCondition(
)
{
m_interface->push();
- m_interface->addAssertion(_condition);
+ addPathConjoinedExpression(_condition);
vector<smt::Expression> expressionsToEvaluate;
vector<string> expressionNames;
@@ -433,19 +562,13 @@ void SMTChecker::checkCondition(
}
smt::CheckResult result;
vector<string> values;
- try
- {
- tie(result, values) = m_interface->check(expressionsToEvaluate);
- }
- catch (smt::SolverError const& _e)
- {
- string description("Error querying SMT solver");
- if (_e.comment())
- description += ": " + *_e.comment();
- m_errorReporter.warning(_location, description);
- return;
- }
+ tie(result, values) = checkSatisfiableAndGenerateModel(expressionsToEvaluate);
+ string conditionalComment;
+ if (m_conditionalExecutionHappened)
+ conditionalComment =
+ "\nNote that some information is erased after conditional execution of parts of the code.\n"
+ "You can re-introduce information using require().";
switch (result)
{
case smt::CheckResult::SATISFIABLE:
@@ -457,27 +580,17 @@ void SMTChecker::checkCondition(
message << " for:\n";
solAssert(values.size() == expressionNames.size(), "");
for (size_t i = 0; i < values.size(); ++i)
- {
- string formattedValue = values.at(i);
- try
- {
- // Parse and re-format nicely
- formattedValue = formatNumber(bigint(formattedValue));
- }
- catch (...) { }
-
- message << " " << expressionNames.at(i) << " = " << formattedValue << "\n";
- }
+ message << " " << expressionNames.at(i) << " = " << values.at(i) << "\n";
}
else
message << ".";
- m_errorReporter.warning(_location, message.str());
+ m_errorReporter.warning(_location, message.str() + conditionalComment);
break;
}
case smt::CheckResult::UNSATISFIABLE:
break;
case smt::CheckResult::UNKNOWN:
- m_errorReporter.warning(_location, _description + " might happen here.");
+ m_errorReporter.warning(_location, _description + " might happen here." + conditionalComment);
break;
case smt::CheckResult::ERROR:
m_errorReporter.warning(_location, "Error trying to invoke SMT solver.");
@@ -488,7 +601,126 @@ void SMTChecker::checkCondition(
m_interface->pop();
}
-void SMTChecker::createVariable(VariableDeclaration const& _varDecl, bool _setToZero)
+void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string const& _description)
+{
+ // Do not check for const-ness if this is a constant.
+ if (dynamic_cast<Literal const*>(&_condition))
+ return;
+
+ m_interface->push();
+ addPathConjoinedExpression(expr(_condition));
+ auto positiveResult = checkSatisfiable();
+ m_interface->pop();
+
+ m_interface->push();
+ addPathConjoinedExpression(!expr(_condition));
+ auto negatedResult = checkSatisfiable();
+ m_interface->pop();
+
+ if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR)
+ m_errorReporter.warning(_condition.location(), "Error trying to invoke SMT solver.");
+ else if (positiveResult == smt::CheckResult::SATISFIABLE && negatedResult == smt::CheckResult::SATISFIABLE)
+ {
+ // everything fine.
+ }
+ else if (positiveResult == smt::CheckResult::UNSATISFIABLE && negatedResult == smt::CheckResult::UNSATISFIABLE)
+ m_errorReporter.warning(_condition.location(), "Condition unreachable.");
+ else
+ {
+ string value;
+ if (positiveResult == smt::CheckResult::SATISFIABLE)
+ {
+ solAssert(negatedResult == smt::CheckResult::UNSATISFIABLE, "");
+ value = "true";
+ }
+ else
+ {
+ solAssert(positiveResult == smt::CheckResult::UNSATISFIABLE, "");
+ solAssert(negatedResult == smt::CheckResult::SATISFIABLE, "");
+ value = "false";
+ }
+ m_errorReporter.warning(_condition.location(), boost::algorithm::replace_all_copy(_description, "$VALUE", value));
+ }
+}
+
+pair<smt::CheckResult, vector<string>>
+SMTChecker::checkSatisfiableAndGenerateModel(vector<smt::Expression> const& _expressionsToEvaluate)
+{
+ smt::CheckResult result;
+ vector<string> values;
+ try
+ {
+ tie(result, values) = m_interface->check(_expressionsToEvaluate);
+ }
+ catch (smt::SolverError const& _e)
+ {
+ string description("Error querying SMT solver");
+ if (_e.comment())
+ description += ": " + *_e.comment();
+ m_errorReporter.warning(description);
+ result = smt::CheckResult::ERROR;
+ }
+
+ for (string& value: values)
+ {
+ try
+ {
+ // Parse and re-format nicely
+ value = formatNumber(bigint(value));
+ }
+ catch (...) { }
+ }
+
+ return make_pair(result, values);
+}
+
+smt::CheckResult SMTChecker::checkSatisfiable()
+{
+ return checkSatisfiableAndGenerateModel({}).first;
+}
+
+void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function)
+{
+ for (auto const& variable: _function.localVariables())
+ if (createVariable(*variable))
+ setZeroValue(*variable);
+
+ for (auto const& param: _function.parameters())
+ if (createVariable(*param))
+ setUnknownValue(*param);
+
+ if (_function.returnParameterList())
+ for (auto const& retParam: _function.returnParameters())
+ if (createVariable(*retParam))
+ setZeroValue(*retParam);
+}
+
+void SMTChecker::resetVariables(vector<Declaration const*> _variables)
+{
+ for (auto const* decl: _variables)
+ {
+ newValue(*decl);
+ setUnknownValue(*decl);
+ }
+}
+
+void SMTChecker::mergeVariables(vector<Declaration const*> const& _variables, smt::Expression const& _condition, VariableSequenceCounters const& _countersEndTrue, VariableSequenceCounters const& _countersEndFalse)
+{
+ set<Declaration const*> uniqueVars(_variables.begin(), _variables.end());
+ for (auto const* decl: uniqueVars)
+ {
+ int trueCounter = _countersEndTrue.at(decl);
+ int falseCounter = _countersEndFalse.at(decl);
+ solAssert(trueCounter != falseCounter, "");
+ m_interface->addAssertion(newValue(*decl) == smt::Expression::ite(
+ _condition,
+ valueAtSequence(*decl, trueCounter),
+ valueAtSequence(*decl, falseCounter))
+ );
+ }
+}
+
+bool SMTChecker::createVariable(VariableDeclaration const& _varDecl)
{
if (dynamic_cast<IntegerType const*>(_varDecl.type().get()))
{
@@ -498,13 +730,16 @@ void SMTChecker::createVariable(VariableDeclaration const& _varDecl, bool _setTo
m_currentSequenceCounter[&_varDecl] = 0;
m_nextFreeSequenceCounter[&_varDecl] = 1;
m_variables.emplace(&_varDecl, m_interface->newFunction(uniqueSymbol(_varDecl), smt::Sort::Int, smt::Sort::Int));
- setValue(_varDecl, _setToZero);
+ return true;
}
else
+ {
m_errorReporter.warning(
_varDecl.location(),
"Assertion checker does not yet support the type of this variable."
);
+ return false;
+ }
}
string SMTChecker::uniqueSymbol(Declaration const& _decl)
@@ -535,23 +770,22 @@ smt::Expression SMTChecker::valueAtSequence(const Declaration& _decl, int _seque
smt::Expression SMTChecker::newValue(Declaration const& _decl)
{
- solAssert(m_currentSequenceCounter.count(&_decl), "");
solAssert(m_nextFreeSequenceCounter.count(&_decl), "");
m_currentSequenceCounter[&_decl] = m_nextFreeSequenceCounter[&_decl]++;
return currentValue(_decl);
}
-void SMTChecker::setValue(Declaration const& _decl, bool _setToZero)
+void SMTChecker::setZeroValue(Declaration const& _decl)
{
- auto const& intType = dynamic_cast<IntegerType const&>(*_decl.type());
+ solAssert(_decl.type()->category() == Type::Category::Integer, "");
+ m_interface->addAssertion(currentValue(_decl) == 0);
+}
- if (_setToZero)
- m_interface->addAssertion(currentValue(_decl) == 0);
- else
- {
- m_interface->addAssertion(currentValue(_decl) >= minValue(intType));
- m_interface->addAssertion(currentValue(_decl) <= maxValue(intType));
- }
+void SMTChecker::setUnknownValue(Declaration const& _decl)
+{
+ auto const& intType = dynamic_cast<IntegerType const&>(*_decl.type());
+ m_interface->addAssertion(currentValue(_decl) >= minValue(intType));
+ m_interface->addAssertion(currentValue(_decl) <= maxValue(intType));
}
smt::Expression SMTChecker::minValue(IntegerType const& _t)
@@ -568,6 +802,18 @@ smt::Expression SMTChecker::expr(Expression const& _e)
{
if (!m_expressions.count(&_e))
{
+ m_errorReporter.warning(_e.location(), "Internal error: Expression undefined for SMT solver." );
+ createExpr(_e);
+ }
+ return m_expressions.at(&_e);
+}
+
+void SMTChecker::createExpr(Expression const& _e)
+{
+ if (m_expressions.count(&_e))
+ m_errorReporter.warning(_e.location(), "Internal error: Expression created twice in SMT solver." );
+ else
+ {
solAssert(_e.annotation().type, "");
switch (_e.annotation().type->category())
{
@@ -588,7 +834,12 @@ smt::Expression SMTChecker::expr(Expression const& _e)
solAssert(false, "Type not implemented.");
}
}
- return m_expressions.at(&_e);
+}
+
+void SMTChecker::defineExpr(Expression const& _e, smt::Expression _value)
+{
+ createExpr(_e);
+ m_interface->addAssertion(expr(_e) == _value);
}
smt::Expression SMTChecker::var(Declaration const& _decl)
@@ -596,3 +847,31 @@ smt::Expression SMTChecker::var(Declaration const& _decl)
solAssert(m_variables.count(&_decl), "");
return m_variables.at(&_decl);
}
+
+void SMTChecker::popPathCondition()
+{
+ solAssert(m_pathConditions.size() > 0, "Cannot pop path condition, empty.");
+ m_pathConditions.pop_back();
+}
+
+void SMTChecker::pushPathCondition(smt::Expression const& _e)
+{
+ m_pathConditions.push_back(currentPathConditions() && _e);
+}
+
+smt::Expression SMTChecker::currentPathConditions()
+{
+ if (m_pathConditions.size() == 0)
+ return smt::Expression(true);
+ return m_pathConditions.back();
+}
+
+void SMTChecker::addPathConjoinedExpression(smt::Expression const& _e)
+{
+ m_interface->addAssertion(currentPathConditions() && _e);
+}
+
+void SMTChecker::addPathImpliedExpression(smt::Expression const& _e)
+{
+ m_interface->addAssertion(smt::Expression::implies(currentPathConditions(), _e));
+}
diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h
index faaac639..b57f0f96 100644
--- a/libsolidity/formal/SMTChecker.h
+++ b/libsolidity/formal/SMTChecker.h
@@ -17,18 +17,23 @@
#pragma once
-#include <libsolidity/ast/ASTVisitor.h>
+
#include <libsolidity/formal/SolverInterface.h>
+
+#include <libsolidity/ast/ASTVisitor.h>
+
#include <libsolidity/interface/ReadFile.h>
#include <map>
#include <string>
+#include <vector>
namespace dev
{
namespace solidity
{
+class VariableUsage;
class ErrorReporter;
class SMTChecker: private ASTConstVisitor
@@ -48,10 +53,12 @@ private:
virtual void endVisit(FunctionDefinition const& _node) override;
virtual bool visit(IfStatement const& _node) override;
virtual bool visit(WhileStatement const& _node) override;
+ virtual bool visit(ForStatement const& _node) override;
virtual void endVisit(VariableDeclarationStatement const& _node) override;
virtual void endVisit(ExpressionStatement const& _node) override;
virtual void endVisit(Assignment const& _node) override;
virtual void endVisit(TupleExpression const& _node) override;
+ virtual void endVisit(UnaryOperation const& _node) override;
virtual void endVisit(BinaryOperation const& _node) override;
virtual void endVisit(FunctionCall const& _node) override;
virtual void endVisit(Identifier const& _node) override;
@@ -61,6 +68,23 @@ private:
void compareOperation(BinaryOperation const& _op);
void booleanOperation(BinaryOperation const& _op);
+ /// Division expression in the given type. Requires special treatment because
+ /// of rounding for signed division.
+ smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type);
+
+ void assignment(Declaration const& _variable, Expression const& _value, SourceLocation const& _location);
+ void assignment(Declaration const& _variable, smt::Expression const& _value, SourceLocation const& _location);
+
+ /// Maps a variable to an SSA index.
+ using VariableSequenceCounters = std::map<Declaration const*, int>;
+
+ /// Visits the branch given by the statement, pushes and pops the current path conditions.
+ /// @param _condition if present, asserts that this condition is true within the branch.
+ /// @returns the variable sequence counter after visiting the branch.
+ VariableSequenceCounters visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr);
+ VariableSequenceCounters visitBranch(Statement const& _statement, smt::Expression _condition);
+
+ /// Check that a condition can be satisfied.
void checkCondition(
smt::Expression _condition,
SourceLocation const& _location,
@@ -68,8 +92,31 @@ private:
std::string const& _additionalValueName = "",
smt::Expression* _additionalValue = nullptr
);
+ /// Checks that a boolean condition is not constant. Do not warn if the expression
+ /// is a literal constant.
+ /// @param _description the warning string, $VALUE will be replaced by the constant value.
+ void checkBooleanNotConstant(
+ Expression const& _condition,
+ std::string const& _description
+ );
+ /// Checks that the value is in the range given by the type.
+ void checkUnderOverflow(smt::Expression _value, IntegerType const& _Type, SourceLocation const& _location);
+
- void createVariable(VariableDeclaration const& _varDecl, bool _setToZero);
+ std::pair<smt::CheckResult, std::vector<std::string>>
+ checkSatisfiableAndGenerateModel(std::vector<smt::Expression> const& _expressionsToEvaluate);
+
+ smt::CheckResult checkSatisfiable();
+
+ void initializeLocalVariables(FunctionDefinition const& _function);
+ void resetVariables(std::vector<Declaration const*> _variables);
+ /// Given two different branches and the touched variables,
+ /// merge the touched variables into after-branch ite variables
+ /// using the branch condition as guard.
+ void mergeVariables(std::vector<Declaration const*> const& _variables, smt::Expression const& _condition, VariableSequenceCounters const& _countersEndTrue, VariableSequenceCounters const& _countersEndFalse);
+ /// Tries to create an uninitialized variable and returns true on success.
+ /// This fails if the type is not supported.
+ bool createVariable(VariableDeclaration const& _varDecl);
static std::string uniqueSymbol(Declaration const& _decl);
static std::string uniqueSymbol(Expression const& _expr);
@@ -87,24 +134,43 @@ private:
/// sequence number to this value and returns the expression.
smt::Expression newValue(Declaration const& _decl);
- /// Sets the value of the declaration either to zero or to its intrinsic range.
- void setValue(Declaration const& _decl, bool _setToZero);
+ /// Sets the value of the declaration to zero.
+ void setZeroValue(Declaration const& _decl);
+ /// Resets the variable to an unknown value (in its range).
+ void setUnknownValue(Declaration const& decl);
static smt::Expression minValue(IntegerType const& _t);
static smt::Expression maxValue(IntegerType const& _t);
- /// Returns the expression corresponding to the AST node. Creates a new expression
- /// if it does not exist yet.
+ /// Returns the expression corresponding to the AST node. Throws if the expression does not exist.
smt::Expression expr(Expression const& _e);
+ /// Creates the expression (value can be arbitrary)
+ void createExpr(Expression const& _e);
+ /// Creates the expression and sets its value.
+ void defineExpr(Expression const& _e, smt::Expression _value);
/// Returns the function declaration corresponding to the given variable.
/// The function takes one argument which is the "sequence number".
smt::Expression var(Declaration const& _decl);
+ /// Adds a new path condition
+ void pushPathCondition(smt::Expression const& _e);
+ /// Remove the last path condition
+ void popPathCondition();
+ /// Returns the conjunction of all path conditions or True if empty
+ smt::Expression currentPathConditions();
+ /// Conjoin the current path conditions with the given parameter and add to the solver
+ void addPathConjoinedExpression(smt::Expression const& _e);
+ /// Add to the solver: the given expression implied by the current path conditions
+ void addPathImpliedExpression(smt::Expression const& _e);
+
std::shared_ptr<smt::SolverInterface> m_interface;
+ std::shared_ptr<VariableUsage> m_variableUsage;
+ bool m_conditionalExecutionHappened = false;
std::map<Declaration const*, int> m_currentSequenceCounter;
std::map<Declaration const*, int> m_nextFreeSequenceCounter;
std::map<Expression const*, smt::Expression> m_expressions;
std::map<Declaration const*, smt::Expression> m_variables;
+ std::vector<smt::Expression> m_pathConditions;
ErrorReporter& m_errorReporter;
FunctionDefinition const* m_currentFunction = nullptr;
diff --git a/libsolidity/formal/SMTLib2Interface.cpp b/libsolidity/formal/SMTLib2Interface.cpp
index c627057a..0e00665a 100644
--- a/libsolidity/formal/SMTLib2Interface.cpp
+++ b/libsolidity/formal/SMTLib2Interface.cpp
@@ -64,8 +64,6 @@ void SMTLib2Interface::pop()
Expression SMTLib2Interface::newFunction(string _name, Sort _domain, Sort _codomain)
{
- solAssert(!m_variables.count(_name), "");
- m_variables[_name] = SMTVariableType::Function;
write(
"(declare-fun |" +
_name +
@@ -80,16 +78,12 @@ Expression SMTLib2Interface::newFunction(string _name, Sort _domain, Sort _codom
Expression SMTLib2Interface::newInteger(string _name)
{
- solAssert(!m_variables.count(_name), "");
- m_variables[_name] = SMTVariableType::Integer;
write("(declare-const |" + _name + "| Int)");
return SolverInterface::newInteger(move(_name));
}
Expression SMTLib2Interface::newBool(string _name)
{
- solAssert(!m_variables.count(_name), "");
- m_variables[_name] = SMTVariableType::Bool;
write("(declare-const |" + _name + "| Bool)");
return SolverInterface::newBool(std::move(_name));
}
@@ -151,9 +145,8 @@ string SMTLib2Interface::checkSatAndGetValuesCommand(vector<Expression> const& _
for (size_t i = 0; i < _expressionsToEvaluate.size(); i++)
{
auto const& e = _expressionsToEvaluate.at(i);
- solAssert(m_variables.count(e.name), "");
- solAssert(m_variables[e.name] == SMTVariableType::Integer, "");
- command += "(declare-const |EVALEXPR_" + to_string(i) + "| Int)\n";
+ solAssert(e.sort == Sort::Int || e.sort == Sort::Bool, "Invalid sort for expression to evaluate.");
+ command += "(declare-const |EVALEXPR_" + to_string(i) + "| " + (e.sort == Sort::Int ? "Int" : "Bool") + "\n";
command += "(assert (= |EVALEXPR_" + to_string(i) + "| " + toSExpr(e) + "))\n";
}
command += "(check-sat)\n";
diff --git a/libsolidity/formal/SMTLib2Interface.h b/libsolidity/formal/SMTLib2Interface.h
index e827449f..63188acd 100644
--- a/libsolidity/formal/SMTLib2Interface.h
+++ b/libsolidity/formal/SMTLib2Interface.h
@@ -68,14 +68,6 @@ private:
ReadCallback::Callback m_queryCallback;
std::vector<std::string> m_accumulatedOutput;
-
- enum class SMTVariableType {
- Function,
- Integer,
- Bool
- };
-
- std::map<std::string,SMTVariableType> m_variables;
};
}
diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h
index 70dc1585..88487310 100644
--- a/libsolidity/formal/SolverInterface.h
+++ b/libsolidity/formal/SolverInterface.h
@@ -44,7 +44,9 @@ enum class CheckResult
enum class Sort
{
- Int, Bool
+ Int,
+ Bool,
+ IntIntFun // Function of one Int returning a single Int
};
/// C++ representation of an SMTLIB2 expression.
@@ -52,9 +54,10 @@ class Expression
{
friend class SolverInterface;
public:
- Expression(size_t _number): name(std::to_string(_number)) {}
- Expression(u256 const& _number): name(_number.str()) {}
- Expression(bigint const& _number): name(_number.str()) {}
+ explicit Expression(bool _v): name(_v ? "true" : "false"), sort(Sort::Bool) {}
+ Expression(size_t _number): name(std::to_string(_number)), sort(Sort::Int) {}
+ Expression(u256 const& _number): name(_number.str()), sort(Sort::Int) {}
+ Expression(bigint const& _number): name(_number.str()), sort(Sort::Int) {}
Expression(Expression const&) = default;
Expression(Expression&&) = default;
@@ -63,26 +66,32 @@ public:
static Expression ite(Expression _condition, Expression _trueValue, Expression _falseValue)
{
+ solAssert(_trueValue.sort == _falseValue.sort, "");
return Expression("ite", std::vector<Expression>{
std::move(_condition), std::move(_trueValue), std::move(_falseValue)
- });
+ }, _trueValue.sort);
+ }
+
+ static Expression implies(Expression _a, Expression _b)
+ {
+ return !std::move(_a) || std::move(_b);
}
friend Expression operator!(Expression _a)
{
- return Expression("not", std::move(_a));
+ return Expression("not", std::move(_a), Sort::Bool);
}
friend Expression operator&&(Expression _a, Expression _b)
{
- return Expression("and", std::move(_a), std::move(_b));
+ return Expression("and", std::move(_a), std::move(_b), Sort::Bool);
}
friend Expression operator||(Expression _a, Expression _b)
{
- return Expression("or", std::move(_a), std::move(_b));
+ return Expression("or", std::move(_a), std::move(_b), Sort::Bool);
}
friend Expression operator==(Expression _a, Expression _b)
{
- return Expression("=", std::move(_a), std::move(_b));
+ return Expression("=", std::move(_a), std::move(_b), Sort::Bool);
}
friend Expression operator!=(Expression _a, Expression _b)
{
@@ -90,52 +99,60 @@ public:
}
friend Expression operator<(Expression _a, Expression _b)
{
- return Expression("<", std::move(_a), std::move(_b));
+ return Expression("<", std::move(_a), std::move(_b), Sort::Bool);
}
friend Expression operator<=(Expression _a, Expression _b)
{
- return Expression("<=", std::move(_a), std::move(_b));
+ return Expression("<=", std::move(_a), std::move(_b), Sort::Bool);
}
friend Expression operator>(Expression _a, Expression _b)
{
- return Expression(">", std::move(_a), std::move(_b));
+ return Expression(">", std::move(_a), std::move(_b), Sort::Bool);
}
friend Expression operator>=(Expression _a, Expression _b)
{
- return Expression(">=", std::move(_a), std::move(_b));
+ return Expression(">=", std::move(_a), std::move(_b), Sort::Bool);
}
friend Expression operator+(Expression _a, Expression _b)
{
- return Expression("+", std::move(_a), std::move(_b));
+ return Expression("+", std::move(_a), std::move(_b), Sort::Int);
}
friend Expression operator-(Expression _a, Expression _b)
{
- return Expression("-", std::move(_a), std::move(_b));
+ return Expression("-", std::move(_a), std::move(_b), Sort::Int);
}
friend Expression operator*(Expression _a, Expression _b)
{
- return Expression("*", std::move(_a), std::move(_b));
+ return Expression("*", std::move(_a), std::move(_b), Sort::Int);
+ }
+ friend Expression operator/(Expression _a, Expression _b)
+ {
+ return Expression("/", std::move(_a), std::move(_b), Sort::Int);
}
Expression operator()(Expression _a) const
{
- solAssert(arguments.empty(), "Attempted function application to non-function.");
- return Expression(name, _a);
+ solAssert(
+ sort == Sort::IntIntFun && arguments.empty(),
+ "Attempted function application to non-function."
+ );
+ return Expression(name, _a, Sort::Int);
}
std::string const name;
std::vector<Expression> const arguments;
+ Sort sort;
private:
/// Manual constructor, should only be used by SolverInterface and this class itself.
- Expression(std::string _name, std::vector<Expression> _arguments):
- name(std::move(_name)), arguments(std::move(_arguments)) {}
-
- explicit Expression(std::string _name):
- Expression(std::move(_name), std::vector<Expression>{}) {}
- Expression(std::string _name, Expression _arg):
- Expression(std::move(_name), std::vector<Expression>{std::move(_arg)}) {}
- Expression(std::string _name, Expression _arg1, Expression _arg2):
- Expression(std::move(_name), std::vector<Expression>{std::move(_arg1), std::move(_arg2)}) {}
+ Expression(std::string _name, std::vector<Expression> _arguments, Sort _sort):
+ name(std::move(_name)), arguments(std::move(_arguments)), sort(_sort) {}
+
+ explicit Expression(std::string _name, Sort _sort):
+ Expression(std::move(_name), std::vector<Expression>{}, _sort) {}
+ Expression(std::string _name, Expression _arg, Sort _sort):
+ Expression(std::move(_name), std::vector<Expression>{std::move(_arg)}, _sort) {}
+ Expression(std::string _name, Expression _arg1, Expression _arg2, Sort _sort):
+ Expression(std::move(_name), std::vector<Expression>{std::move(_arg1), std::move(_arg2)}, _sort) {}
};
DEV_SIMPLE_EXCEPTION(SolverError);
@@ -148,20 +165,21 @@ public:
virtual void push() = 0;
virtual void pop() = 0;
- virtual Expression newFunction(std::string _name, Sort /*_domain*/, Sort /*_codomain*/)
+ virtual Expression newFunction(std::string _name, Sort _domain, Sort _codomain)
{
+ solAssert(_domain == Sort::Int && _codomain == Sort::Int, "Function sort not supported.");
// Subclasses should do something here
- return Expression(std::move(_name), {});
+ return Expression(std::move(_name), {}, Sort::IntIntFun);
}
virtual Expression newInteger(std::string _name)
{
// Subclasses should do something here
- return Expression(std::move(_name), {});
+ return Expression(std::move(_name), {}, Sort::Int);
}
virtual Expression newBool(std::string _name)
{
// Subclasses should do something here
- return Expression(std::move(_name), {});
+ return Expression(std::move(_name), {}, Sort::Bool);
}
virtual void addAssertion(Expression const& _expr) = 0;
diff --git a/libsolidity/formal/VariableUsage.cpp b/libsolidity/formal/VariableUsage.cpp
new file mode 100644
index 00000000..4e96059d
--- /dev/null
+++ b/libsolidity/formal/VariableUsage.cpp
@@ -0,0 +1,80 @@
+/*
+ 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/>.
+*/
+
+#include <libsolidity/formal/VariableUsage.h>
+
+#include <libsolidity/ast/ASTVisitor.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+VariableUsage::VariableUsage(ASTNode const& _node)
+{
+ auto nodeFun = [&](ASTNode const& n) -> bool
+ {
+ if (Identifier const* identifier = dynamic_cast<decltype(identifier)>(&n))
+ {
+ Declaration const* declaration = identifier->annotation().referencedDeclaration;
+ solAssert(declaration, "");
+ if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
+ if (
+ varDecl->isLocalVariable() &&
+ identifier->annotation().lValueRequested &&
+ varDecl->annotation().type->isValueType()
+ )
+ m_touchedVariable[&n] = varDecl;
+ }
+ return true;
+ };
+ auto edgeFun = [&](ASTNode const& _parent, ASTNode const& _child)
+ {
+ if (m_touchedVariable.count(&_child) || m_children.count(&_child))
+ m_children[&_parent].push_back(&_child);
+ };
+
+ ASTReduce reducer(nodeFun, edgeFun);
+ _node.accept(reducer);
+}
+
+vector<Declaration const*> VariableUsage::touchedVariables(ASTNode const& _node) const
+{
+ if (!m_children.count(&_node) && !m_touchedVariable.count(&_node))
+ return {};
+
+ set<Declaration const*> touched;
+ vector<ASTNode const*> toVisit;
+ toVisit.push_back(&_node);
+
+ while (!toVisit.empty())
+ {
+ ASTNode const* n = toVisit.back();
+ toVisit.pop_back();
+ if (m_children.count(n))
+ {
+ solAssert(!m_touchedVariable.count(n), "");
+ toVisit += m_children.at(n);
+ }
+ else
+ {
+ solAssert(m_touchedVariable.count(n), "");
+ touched.insert(m_touchedVariable.at(n));
+ }
+ }
+
+ return {touched.begin(), touched.end()};
+}
diff --git a/libsolidity/formal/VariableUsage.h b/libsolidity/formal/VariableUsage.h
new file mode 100644
index 00000000..62561cce
--- /dev/null
+++ b/libsolidity/formal/VariableUsage.h
@@ -0,0 +1,50 @@
+/*
+ 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/>.
+*/
+
+#pragma once
+
+#include <map>
+#include <set>
+#include <vector>
+
+namespace dev
+{
+namespace solidity
+{
+
+class ASTNode;
+class Declaration;
+
+/**
+ * This class collects information about which local variables of value type
+ * are modified in which parts of the AST.
+ */
+class VariableUsage
+{
+public:
+ explicit VariableUsage(ASTNode const& _node);
+
+ std::vector<Declaration const*> touchedVariables(ASTNode const& _node) const;
+
+private:
+ // Variable touched by a specific AST node.
+ std::map<ASTNode const*, Declaration const*> m_touchedVariable;
+ std::map<ASTNode const*, std::vector<ASTNode const*>> m_children;
+};
+
+}
+}
diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp
index 6111b2c8..769e6edb 100644
--- a/libsolidity/formal/Z3Interface.cpp
+++ b/libsolidity/formal/Z3Interface.cpp
@@ -91,7 +91,7 @@ pair<CheckResult, vector<string>> Z3Interface::check(vector<Expression> const& _
solAssert(false, "");
}
- if (result != CheckResult::UNSATISFIABLE)
+ if (result != CheckResult::UNSATISFIABLE && !_expressionsToEvaluate.empty())
{
z3::model m = m_solver.get_model();
for (Expression const& e: _expressionsToEvaluate)
@@ -127,7 +127,8 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
{">=", 2},
{"+", 2},
{"-", 2},
- {"*", 2}
+ {"*", 2},
+ {"/", 2}
};
string const& n = _expr.name;
if (m_functions.count(n))
@@ -139,8 +140,13 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
}
else if (arguments.empty())
{
- // We assume it is an integer...
- return m_context.int_val(n.c_str());
+ if (n == "true")
+ return m_context.bool_val(true);
+ else if (n == "false")
+ return m_context.bool_val(false);
+ else
+ // We assume it is an integer...
+ return m_context.int_val(n.c_str());
}
solAssert(arity.count(n) && arity.at(n) == arguments.size(), "");
@@ -168,6 +174,8 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
return arguments[0] - arguments[1];
else if (n == "*")
return arguments[0] * arguments[1];
+ else if (n == "/")
+ return arguments[0] / arguments[1];
// Cannot reach here.
solAssert(false, "");
return arguments[0];
diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp
index e5bdc90f..2d6e58de 100644
--- a/libsolidity/inlineasm/AsmAnalysis.cpp
+++ b/libsolidity/inlineasm/AsmAnalysis.cpp
@@ -54,14 +54,15 @@ bool AsmAnalyzer::analyze(Block const& _block)
bool AsmAnalyzer::operator()(Label const& _label)
{
- solAssert(!m_julia, "");
+ solAssert(m_flavour == AsmFlavour::Loose, "");
m_info.stackHeightInfo[&_label] = m_stackHeight;
+ warnOnInstructions(solidity::Instruction::JUMPDEST, _label.location);
return true;
}
bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction)
{
- solAssert(!m_julia, "");
+ solAssert(m_flavour == AsmFlavour::Loose, "");
auto const& info = instructionInfo(_instruction.instruction);
m_stackHeight += info.ret - info.args;
m_info.stackHeightInfo[&_instruction] = m_stackHeight;
@@ -140,22 +141,34 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
{
- solAssert(!m_julia, "");
+ solAssert(m_flavour != AsmFlavour::IULIA, "");
bool success = true;
for (auto const& arg: _instr.arguments | boost::adaptors::reversed)
if (!expectExpression(arg))
success = false;
// Parser already checks that the number of arguments is correct.
- solAssert(instructionInfo(_instr.instruction.instruction).args == int(_instr.arguments.size()), "");
- if (!(*this)(_instr.instruction))
- success = false;
+ auto const& info = instructionInfo(_instr.instruction);
+ solAssert(info.args == int(_instr.arguments.size()), "");
+ m_stackHeight += info.ret - info.args;
m_info.stackHeightInfo[&_instr] = m_stackHeight;
+ warnOnInstructions(_instr.instruction, _instr.location);
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::ExpressionStatement const& _statement)
+{
+ size_t initialStackHeight = m_stackHeight;
+ bool success = boost::apply_visitor(*this, _statement.expression);
+ if (m_flavour != AsmFlavour::Loose)
+ if (!expectDeposit(0, initialStackHeight, _statement.location))
+ success = false;
+ m_info.stackHeightInfo[&_statement] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment)
{
- solAssert(!m_julia, "");
+ solAssert(m_flavour == AsmFlavour::Loose, "");
bool success = checkAssignment(_assignment.variableName, size_t(-1));
m_info.stackHeightInfo[&_assignment] = m_stackHeight;
return success;
@@ -217,14 +230,14 @@ bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get();
solAssert(virtualBlock, "");
Scope& varScope = scope(virtualBlock);
- for (auto const& var: _funDef.arguments + _funDef.returns)
+ for (auto const& var: _funDef.parameters + _funDef.returnVariables)
{
expectValidType(var.type, var.location);
m_activeVariables.insert(&boost::get<Scope::Variable>(varScope.identifiers.at(var.name)));
}
int const stackHeight = m_stackHeight;
- m_stackHeight = _funDef.arguments.size() + _funDef.returns.size();
+ m_stackHeight = _funDef.parameters.size() + _funDef.returnVariables.size();
bool success = (*this)(_funDef.body);
@@ -286,6 +299,22 @@ bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
return success;
}
+bool AsmAnalyzer::operator()(If const& _if)
+{
+ bool success = true;
+
+ if (!expectExpression(*_if.condition))
+ success = false;
+ m_stackHeight--;
+
+ if (!(*this)(_if.body))
+ success = false;
+
+ m_info.stackHeightInfo[&_if] = m_stackHeight;
+
+ return success;
+}
+
bool AsmAnalyzer::operator()(Switch const& _switch)
{
bool success = true;
@@ -389,13 +418,13 @@ bool AsmAnalyzer::operator()(Block const& _block)
return success;
}
-bool AsmAnalyzer::expectExpression(Statement const& _statement)
+bool AsmAnalyzer::expectExpression(Expression const& _expr)
{
bool success = true;
int const initialHeight = m_stackHeight;
- if (!boost::apply_visitor(*this, _statement))
+ if (!boost::apply_visitor(*this, _expr))
success = false;
- if (!expectDeposit(1, initialHeight, locationOf(_statement)))
+ if (!expectDeposit(1, initialHeight, locationOf(_expr)))
success = false;
return success;
}
@@ -479,7 +508,7 @@ Scope& AsmAnalyzer::scope(Block const* _block)
}
void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location)
{
- if (!m_julia)
+ if (m_flavour != AsmFlavour::IULIA)
return;
if (!builtinTypes.count(type))
@@ -506,11 +535,11 @@ void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocatio
"the Metropolis hard fork. Before that it acts as an invalid instruction."
);
- if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI)
+ if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI || _instr == solidity::Instruction::JUMPDEST)
m_errorReporter.warning(
_location,
- "Jump instructions are low-level EVM features that can lead to "
+ "Jump instructions and labels are low-level EVM features that can lead to "
"incorrect stack access. Because of that they are discouraged. "
- "Please consider using \"switch\" or \"for\" statements instead."
+ "Please consider using \"switch\", \"if\" or \"for\" statements instead."
);
}
diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h
index 9b2a8f9c..7a81dbf8 100644
--- a/libsolidity/inlineasm/AsmAnalysis.h
+++ b/libsolidity/inlineasm/AsmAnalysis.h
@@ -54,9 +54,9 @@ public:
explicit AsmAnalyzer(
AsmAnalysisInfo& _analysisInfo,
ErrorReporter& _errorReporter,
- bool _julia = false,
+ AsmFlavour _flavour = AsmFlavour::Loose,
julia::ExternalIdentifierAccess::Resolver const& _resolver = julia::ExternalIdentifierAccess::Resolver()
- ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_julia(_julia) {}
+ ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_flavour(_flavour) {}
bool analyze(assembly::Block const& _block);
@@ -65,18 +65,20 @@ public:
bool operator()(assembly::Identifier const&);
bool operator()(assembly::FunctionalInstruction const& _functionalInstruction);
bool operator()(assembly::Label const& _label);
+ bool operator()(assembly::ExpressionStatement const&);
bool operator()(assembly::StackAssignment const&);
bool operator()(assembly::Assignment const& _assignment);
bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
bool operator()(assembly::FunctionCall const& _functionCall);
+ bool operator()(assembly::If const& _if);
bool operator()(assembly::Switch const& _switch);
bool operator()(assembly::ForLoop const& _forLoop);
bool operator()(assembly::Block const& _block);
private:
/// Visits the statement and expects it to deposit one item onto the stack.
- bool expectExpression(Statement const& _statement);
+ bool expectExpression(Expression const& _expr);
bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location);
/// Verifies that a variable to be assigned to exists and has the same size
@@ -95,7 +97,7 @@ private:
std::set<Scope::Variable const*> m_activeVariables;
AsmAnalysisInfo& m_info;
ErrorReporter& m_errorReporter;
- bool m_julia = false;
+ AsmFlavour m_flavour = AsmFlavour::Loose;
};
}
diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h
index b0dd85ca..2982d5e0 100644
--- a/libsolidity/inlineasm/AsmData.h
+++ b/libsolidity/inlineasm/AsmData.h
@@ -58,21 +58,25 @@ struct StackAssignment { SourceLocation location; Identifier variableName; };
/// Multiple assignment ("x, y := f()"), where the left hand side variables each occupy
/// a single stack slot and expects a single expression on the right hand returning
/// the same amount of items as the number of variables.
-struct Assignment { SourceLocation location; std::vector<Identifier> variableNames; std::shared_ptr<Statement> value; };
+struct Assignment { SourceLocation location; std::vector<Identifier> variableNames; std::shared_ptr<Expression> value; };
/// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))"
-struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; };
-struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Statement> arguments; };
+struct FunctionalInstruction { SourceLocation location; solidity::Instruction instruction; std::vector<Expression> arguments; };
+struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Expression> arguments; };
+/// Statement that contains only a single expression
+struct ExpressionStatement { SourceLocation location; Expression expression; };
/// Block-scope variable declaration ("let x:u256 := mload(20:u256)"), non-hoisted
-struct VariableDeclaration { SourceLocation location; TypedNameList variables; std::shared_ptr<Statement> value; };
+struct VariableDeclaration { SourceLocation location; TypedNameList variables; std::shared_ptr<Expression> 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; TypedNameList arguments; TypedNameList returns; Block body; };
+struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList parameters; TypedNameList returnVariables; Block body; };
+/// Conditional execution without "else" part.
+struct If { SourceLocation location; std::shared_ptr<Expression> condition; Block body; };
/// Switch case or default case
struct Case { SourceLocation location; std::shared_ptr<Literal> value; Block body; };
/// Switch statement
-struct Switch { SourceLocation location; std::shared_ptr<Statement> expression; std::vector<Case> cases; };
-struct ForLoop { SourceLocation location; Block pre; std::shared_ptr<Statement> condition; Block post; Block body; };
+struct Switch { SourceLocation location; std::shared_ptr<Expression> expression; std::vector<Case> cases; };
+struct ForLoop { SourceLocation location; Block pre; std::shared_ptr<Expression> condition; Block post; Block body; };
struct LocationExtractor: boost::static_visitor<SourceLocation>
{
diff --git a/libsolidity/inlineasm/AsmDataForward.h b/libsolidity/inlineasm/AsmDataForward.h
index 4ead7ff5..3a9600fe 100644
--- a/libsolidity/inlineasm/AsmDataForward.h
+++ b/libsolidity/inlineasm/AsmDataForward.h
@@ -41,11 +41,24 @@ struct VariableDeclaration;
struct FunctionalInstruction;
struct FunctionDefinition;
struct FunctionCall;
+struct If;
struct Switch;
+struct Case;
struct ForLoop;
+struct ExpressionStatement;
struct Block;
-using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, ForLoop, Block>;
+struct TypedName;
+
+using Expression = boost::variant<FunctionalInstruction, FunctionCall, Identifier, Literal>;
+using Statement = boost::variant<ExpressionStatement, Instruction, Label, StackAssignment, Assignment, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>;
+
+enum class AsmFlavour
+{
+ Loose, // no types, EVM instructions as function, jumps and direct stack manipulations
+ Strict, // no types, EVM instructions as functions, but no jumps and no direct stack manipulations
+ IULIA // same as Strict mode with types
+};
}
}
diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp
index 1f4df75b..306b07e6 100644
--- a/libsolidity/inlineasm/AsmParser.cpp
+++ b/libsolidity/inlineasm/AsmParser.cpp
@@ -73,13 +73,19 @@ assembly::Statement Parser::parseStatement()
return parseFunctionDefinition();
case Token::LBrace:
return parseBlock();
+ case Token::If:
+ {
+ assembly::If _if = createWithLocation<assembly::If>();
+ m_scanner->next();
+ _if.condition = make_shared<Expression>(parseExpression());
+ _if.body = parseBlock();
+ return _if;
+ }
case Token::Switch:
{
assembly::Switch _switch = createWithLocation<assembly::Switch>();
m_scanner->next();
- _switch.expression = make_shared<Statement>(parseExpression());
- if (_switch.expression->type() == typeid(assembly::Instruction))
- fatalParserError("Instructions are not supported as expressions for switch.");
+ _switch.expression = make_shared<Expression>(parseExpression());
while (m_scanner->currentToken() == Token::Case)
_switch.cases.emplace_back(parseCase());
if (m_scanner->currentToken() == Token::Default)
@@ -97,14 +103,14 @@ assembly::Statement Parser::parseStatement()
return parseForLoop();
case Token::Assign:
{
- if (m_julia)
+ if (m_flavour != AsmFlavour::Loose)
break;
assembly::StackAssignment assignment = createWithLocation<assembly::StackAssignment>();
advance();
expectToken(Token::Colon);
assignment.variableName.location = location();
assignment.variableName.name = currentLiteral();
- if (!m_julia && instructions().count(assignment.variableName.name))
+ if (instructions().count(assignment.variableName.name))
fatalParserError("Identifier expected, got instruction name.");
assignment.location.end = endPosition();
expectToken(Token::Identifier);
@@ -117,18 +123,21 @@ assembly::Statement Parser::parseStatement()
// Simple instruction (might turn into functional),
// literal,
// identifier (might turn into label or functional assignment)
- Statement statement(parseElementaryOperation(false));
+ ElementaryOperation elementary(parseElementaryOperation());
switch (currentToken())
{
case Token::LParen:
- return parseCall(std::move(statement));
+ {
+ Expression expr = parseCall(std::move(elementary));
+ return ExpressionStatement{locationOf(expr), expr};
+ }
case Token::Comma:
{
// if a comma follows, a multiple assignment is assumed
- if (statement.type() != typeid(assembly::Identifier))
+ if (elementary.type() != typeid(assembly::Identifier))
fatalParserError("Label name / variable name must precede \",\" (multiple assignment).");
- assembly::Identifier const& identifier = boost::get<assembly::Identifier>(statement);
+ assembly::Identifier const& identifier = boost::get<assembly::Identifier>(elementary);
Assignment assignment = createWithLocation<Assignment>(identifier.location);
assignment.variableNames.emplace_back(identifier);
@@ -136,43 +145,43 @@ assembly::Statement Parser::parseStatement()
do
{
expectToken(Token::Comma);
- statement = parseElementaryOperation(false);
- if (statement.type() != typeid(assembly::Identifier))
+ elementary = parseElementaryOperation();
+ if (elementary.type() != typeid(assembly::Identifier))
fatalParserError("Variable name expected in multiple assignemnt.");
- assignment.variableNames.emplace_back(boost::get<assembly::Identifier>(statement));
+ assignment.variableNames.emplace_back(boost::get<assembly::Identifier>(elementary));
}
while (currentToken() == Token::Comma);
expectToken(Token::Colon);
expectToken(Token::Assign);
- assignment.value.reset(new Statement(parseExpression()));
+ assignment.value.reset(new Expression(parseExpression()));
assignment.location.end = locationOf(*assignment.value).end;
return assignment;
}
case Token::Colon:
{
- if (statement.type() != typeid(assembly::Identifier))
+ if (elementary.type() != typeid(assembly::Identifier))
fatalParserError("Label name / variable name must precede \":\".");
- assembly::Identifier const& identifier = boost::get<assembly::Identifier>(statement);
+ assembly::Identifier const& identifier = boost::get<assembly::Identifier>(elementary);
advance();
// identifier:=: should be parsed as identifier: =: (i.e. a label),
// while identifier:= (being followed by a non-colon) as identifier := (assignment).
if (currentToken() == Token::Assign && peekNextToken() != Token::Colon)
{
assembly::Assignment assignment = createWithLocation<assembly::Assignment>(identifier.location);
- if (!m_julia && instructions().count(identifier.name))
+ if (m_flavour != AsmFlavour::IULIA && instructions().count(identifier.name))
fatalParserError("Cannot use instruction names for identifier names.");
advance();
assignment.variableNames.emplace_back(identifier);
- assignment.value.reset(new Statement(parseExpression()));
+ assignment.value.reset(new Expression(parseExpression()));
assignment.location.end = locationOf(*assignment.value).end;
return assignment;
}
else
{
// label
- if (m_julia)
+ if (m_flavour != AsmFlavour::Loose)
fatalParserError("Labels are not supported.");
Label label = createWithLocation<Label>(identifier.location);
label.name = identifier.name;
@@ -180,11 +189,25 @@ assembly::Statement Parser::parseStatement()
}
}
default:
- if (m_julia)
+ if (m_flavour != AsmFlavour::Loose)
fatalParserError("Call or assignment expected.");
break;
}
- return statement;
+ if (elementary.type() == typeid(assembly::Identifier))
+ {
+ Expression expr = boost::get<assembly::Identifier>(elementary);
+ return ExpressionStatement{locationOf(expr), expr};
+ }
+ else if (elementary.type() == typeid(assembly::Literal))
+ {
+ Expression expr = boost::get<assembly::Literal>(elementary);
+ return ExpressionStatement{locationOf(expr), expr};
+ }
+ else
+ {
+ solAssert(elementary.type() == typeid(assembly::Instruction), "Invalid elementary operation.");
+ return boost::get<assembly::Instruction>(elementary);
+ }
}
assembly::Case Parser::parseCase()
@@ -196,10 +219,10 @@ assembly::Case Parser::parseCase()
else if (m_scanner->currentToken() == Token::Case)
{
m_scanner->next();
- assembly::Statement statement = parseElementaryOperation();
- if (statement.type() != typeid(assembly::Literal))
+ ElementaryOperation literal = parseElementaryOperation();
+ if (literal.type() != typeid(assembly::Literal))
fatalParserError("Literal expected.");
- _case.value = make_shared<Literal>(std::move(boost::get<assembly::Literal>(statement)));
+ _case.value = make_shared<Literal>(boost::get<assembly::Literal>(std::move(literal)));
}
else
fatalParserError("Case or default case expected.");
@@ -214,22 +237,39 @@ assembly::ForLoop Parser::parseForLoop()
ForLoop forLoop = createWithLocation<ForLoop>();
expectToken(Token::For);
forLoop.pre = parseBlock();
- forLoop.condition = make_shared<Statement>(parseExpression());
- if (forLoop.condition->type() == typeid(assembly::Instruction))
- fatalParserError("Instructions are not supported as conditions for the for statement.");
+ forLoop.condition = make_shared<Expression>(parseExpression());
forLoop.post = parseBlock();
forLoop.body = parseBlock();
forLoop.location.end = forLoop.body.location.end;
return forLoop;
}
-assembly::Statement Parser::parseExpression()
+assembly::Expression Parser::parseExpression()
{
RecursionGuard recursionGuard(*this);
- Statement operation = parseElementaryOperation(true);
+ // In strict mode, this might parse a plain Instruction, but
+ // it will be converted to a FunctionalInstruction inside
+ // parseCall below.
+ ElementaryOperation operation = parseElementaryOperation();
if (operation.type() == typeid(Instruction))
{
Instruction const& instr = boost::get<Instruction>(operation);
+ // Disallow instructions returning multiple values (and DUP/SWAP) as expression.
+ if (
+ instructionInfo(instr.instruction).ret != 1 ||
+ isDupInstruction(instr.instruction) ||
+ isSwapInstruction(instr.instruction)
+ )
+ fatalParserError(
+ "Instruction \"" +
+ instructionNames().at(instr.instruction) +
+ "\" not allowed in this context."
+ );
+ if (m_flavour != AsmFlavour::Loose && currentToken() != Token::LParen)
+ fatalParserError(
+ "Non-functional instructions are not allowed in this context."
+ );
+ // Enforce functional notation for instructions requiring multiple arguments.
int args = instructionInfo(instr.instruction).args;
if (args > 0 && currentToken() != Token::LParen)
fatalParserError(string(
@@ -242,8 +282,20 @@ assembly::Statement Parser::parseExpression()
}
if (currentToken() == Token::LParen)
return parseCall(std::move(operation));
+ else if (operation.type() == typeid(Instruction))
+ {
+ // Instructions not taking arguments are allowed as expressions.
+ solAssert(m_flavour == AsmFlavour::Loose, "");
+ Instruction& instr = boost::get<Instruction>(operation);
+ return FunctionalInstruction{std::move(instr.location), instr.instruction, {}};
+ }
+ else if (operation.type() == typeid(assembly::Identifier))
+ return boost::get<assembly::Identifier>(operation);
else
- return operation;
+ {
+ solAssert(operation.type() == typeid(assembly::Literal), "");
+ return boost::get<assembly::Literal>(operation);
+ }
}
std::map<string, dev::solidity::Instruction> const& Parser::instructions()
@@ -286,10 +338,10 @@ std::map<dev::solidity::Instruction, string> const& Parser::instructionNames()
return s_instructionNames;
}
-assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
+Parser::ElementaryOperation Parser::parseElementaryOperation()
{
RecursionGuard recursionGuard(*this);
- Statement ret;
+ ElementaryOperation ret;
switch (currentToken())
{
case Token::Identifier:
@@ -307,15 +359,9 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
else
literal = currentLiteral();
// first search the set of instructions.
- if (!m_julia && instructions().count(literal))
+ if (m_flavour != AsmFlavour::IULIA && instructions().count(literal))
{
dev::solidity::Instruction const& instr = instructions().at(literal);
- if (_onlySinglePusher)
- {
- InstructionInfo info = dev::solidity::instructionInfo(instr);
- if (info.ret != 1)
- fatalParserError("Instruction \"" + literal + "\" not allowed in this context.");
- }
ret = Instruction{location(), instr};
}
else
@@ -354,7 +400,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
""
};
advance();
- if (m_julia)
+ if (m_flavour == AsmFlavour::IULIA)
{
expectToken(Token::Colon);
literal.location.end = endPosition();
@@ -367,7 +413,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
}
default:
fatalParserError(
- m_julia ?
+ m_flavour == AsmFlavour::IULIA ?
"Literal or identifier expected." :
"Literal, identifier or instruction expected."
);
@@ -392,7 +438,7 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration()
{
expectToken(Token::Colon);
expectToken(Token::Assign);
- varDecl.value.reset(new Statement(parseExpression()));
+ varDecl.value.reset(new Expression(parseExpression()));
varDecl.location.end = locationOf(*varDecl.value).end;
}
else
@@ -409,7 +455,7 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition()
expectToken(Token::LParen);
while (currentToken() != Token::RParen)
{
- funDef.arguments.emplace_back(parseTypedName());
+ funDef.parameters.emplace_back(parseTypedName());
if (currentToken() == Token::RParen)
break;
expectToken(Token::Comma);
@@ -421,7 +467,7 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition()
expectToken(Token::GreaterThan);
while (true)
{
- funDef.returns.emplace_back(parseTypedName());
+ funDef.returnVariables.emplace_back(parseTypedName());
if (currentToken() == Token::LBrace)
break;
expectToken(Token::Comma);
@@ -432,16 +478,17 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition()
return funDef;
}
-assembly::Statement Parser::parseCall(assembly::Statement&& _instruction)
+assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
{
RecursionGuard recursionGuard(*this);
- if (_instruction.type() == typeid(Instruction))
+ if (_initialOp.type() == typeid(Instruction))
{
- solAssert(!m_julia, "Instructions are invalid in JULIA");
+ solAssert(m_flavour != AsmFlavour::IULIA, "Instructions are invalid in JULIA");
+ Instruction& instruction = boost::get<Instruction>(_initialOp);
FunctionalInstruction ret;
- ret.instruction = std::move(boost::get<Instruction>(_instruction));
- ret.location = ret.instruction.location;
- solidity::Instruction instr = ret.instruction.instruction;
+ ret.instruction = instruction.instruction;
+ ret.location = std::move(instruction.location);
+ solidity::Instruction instr = ret.instruction;
InstructionInfo instrInfo = instructionInfo(instr);
if (solidity::isDupInstruction(instr))
fatalParserError("DUPi instructions not allowed for functional notation");
@@ -488,10 +535,10 @@ assembly::Statement Parser::parseCall(assembly::Statement&& _instruction)
expectToken(Token::RParen);
return ret;
}
- else if (_instruction.type() == typeid(Identifier))
+ else if (_initialOp.type() == typeid(Identifier))
{
FunctionCall ret;
- ret.functionName = std::move(boost::get<Identifier>(_instruction));
+ ret.functionName = std::move(boost::get<Identifier>(_initialOp));
ret.location = ret.functionName.location;
expectToken(Token::LParen);
while (currentToken() != Token::RParen)
@@ -507,7 +554,7 @@ assembly::Statement Parser::parseCall(assembly::Statement&& _instruction)
}
else
fatalParserError(
- m_julia ?
+ m_flavour == AsmFlavour::IULIA ?
"Function name expected." :
"Assembly instruction or function name required in front of \"(\")"
);
@@ -520,7 +567,7 @@ TypedName Parser::parseTypedName()
RecursionGuard recursionGuard(*this);
TypedName typedName = createWithLocation<TypedName>();
typedName.name = expectAsmIdentifier();
- if (m_julia)
+ if (m_flavour == AsmFlavour::IULIA)
{
expectToken(Token::Colon);
typedName.location.end = endPosition();
@@ -532,12 +579,18 @@ TypedName Parser::parseTypedName()
string Parser::expectAsmIdentifier()
{
string name = currentLiteral();
- if (m_julia)
+ if (m_flavour == AsmFlavour::IULIA)
{
- if (currentToken() == Token::Bool)
+ switch (currentToken())
{
+ case Token::Return:
+ case Token::Byte:
+ case Token::Address:
+ case Token::Bool:
advance();
return name;
+ default:
+ break;
}
}
else if (instructions().count(name))
diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h
index e46d1732..015aeef3 100644
--- a/libsolidity/inlineasm/AsmParser.h
+++ b/libsolidity/inlineasm/AsmParser.h
@@ -37,13 +37,16 @@ namespace assembly
class Parser: public ParserBase
{
public:
- explicit Parser(ErrorReporter& _errorReporter, bool _julia = false): ParserBase(_errorReporter), m_julia(_julia) {}
+ explicit Parser(ErrorReporter& _errorReporter, AsmFlavour _flavour = AsmFlavour::Loose):
+ ParserBase(_errorReporter), m_flavour(_flavour) {}
/// Parses an inline assembly block starting with `{` and ending with `}`.
/// @returns an empty shared pointer on error.
std::shared_ptr<Block> parse(std::shared_ptr<Scanner> const& _scanner);
protected:
+ using ElementaryOperation = boost::variant<assembly::Instruction, assembly::Literal, assembly::Identifier>;
+
/// Creates an inline assembly node with the given source location.
template <class T> T createWithLocation(SourceLocation const& _loc = SourceLocation()) const
{
@@ -65,20 +68,23 @@ protected:
Case parseCase();
ForLoop parseForLoop();
/// Parses a functional expression that has to push exactly one stack element
- Statement parseExpression();
+ assembly::Expression parseExpression();
static std::map<std::string, dev::solidity::Instruction> const& instructions();
static std::map<dev::solidity::Instruction, std::string> const& instructionNames();
- Statement parseElementaryOperation(bool _onlySinglePusher = false);
+ /// Parses an elementary operation, i.e. a literal, identifier or instruction.
+ /// This will parse instructions even in strict mode as part of the full parser
+ /// for FunctionalInstruction.
+ ElementaryOperation parseElementaryOperation();
VariableDeclaration parseVariableDeclaration();
FunctionDefinition parseFunctionDefinition();
- Statement parseCall(Statement&& _instruction);
+ assembly::Expression parseCall(ElementaryOperation&& _initialOp);
TypedName parseTypedName();
std::string expectAsmIdentifier();
static bool isValidNumberLiteral(std::string const& _literal);
private:
- bool m_julia = false;
+ AsmFlavour m_flavour = AsmFlavour::Loose;
};
}
diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp
index a5272808..bacd7a94 100644
--- a/libsolidity/inlineasm/AsmPrinter.cpp
+++ b/libsolidity/inlineasm/AsmPrinter.cpp
@@ -94,7 +94,7 @@ string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functional
{
solAssert(!m_julia, "");
return
- (*this)(_functionalInstruction.instruction) +
+ boost::to_lower_copy(instructionInfo(_functionalInstruction.instruction).name) +
"(" +
boost::algorithm::join(
_functionalInstruction.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)),
@@ -102,6 +102,11 @@ string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functional
")";
}
+string AsmPrinter::operator()(ExpressionStatement const& _statement)
+{
+ return boost::apply_visitor(*this, _statement.expression);
+}
+
string AsmPrinter::operator()(assembly::Label const& _label)
{
solAssert(!m_julia, "");
@@ -144,17 +149,17 @@ string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefin
{
string out = "function " + _functionDefinition.name + "(";
out += boost::algorithm::join(
- _functionDefinition.arguments | boost::adaptors::transformed(
+ _functionDefinition.parameters | boost::adaptors::transformed(
[this](TypedName argument) { return argument.name + appendTypeName(argument.type); }
),
", "
);
out += ")";
- if (!_functionDefinition.returns.empty())
+ if (!_functionDefinition.returnVariables.empty())
{
out += " -> ";
out += boost::algorithm::join(
- _functionDefinition.returns | boost::adaptors::transformed(
+ _functionDefinition.returnVariables | boost::adaptors::transformed(
[this](TypedName argument) { return argument.name + appendTypeName(argument.type); }
),
", "
@@ -174,6 +179,11 @@ string AsmPrinter::operator()(assembly::FunctionCall const& _functionCall)
")";
}
+string AsmPrinter::operator()(If const& _if)
+{
+ return "if " + boost::apply_visitor(*this, *_if.condition) + "\n" + (*this)(_if.body);
+}
+
string AsmPrinter::operator()(Switch const& _switch)
{
string out = "switch " + boost::apply_visitor(*this, *_switch.expression);
diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h
index 66520632..5bd87aba 100644
--- a/libsolidity/inlineasm/AsmPrinter.h
+++ b/libsolidity/inlineasm/AsmPrinter.h
@@ -42,12 +42,14 @@ public:
std::string operator()(assembly::Literal const& _literal);
std::string operator()(assembly::Identifier const& _identifier);
std::string operator()(assembly::FunctionalInstruction const& _functionalInstruction);
+ std::string operator()(assembly::ExpressionStatement const& _expr);
std::string operator()(assembly::Label const& _label);
std::string operator()(assembly::StackAssignment const& _assignment);
std::string operator()(assembly::Assignment const& _assignment);
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::If const& _if);
std::string operator()(assembly::Switch const& _switch);
std::string operator()(assembly::ForLoop const& _forLoop);
std::string operator()(assembly::Block const& _block);
diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp
index b70ae9ac..86f3809c 100644
--- a/libsolidity/inlineasm/AsmScopeFiller.cpp
+++ b/libsolidity/inlineasm/AsmScopeFiller.cpp
@@ -45,6 +45,11 @@ ScopeFiller::ScopeFiller(AsmAnalysisInfo& _info, ErrorReporter& _errorReporter):
m_currentScope = &scope(nullptr);
}
+bool ScopeFiller::operator()(ExpressionStatement const& _expr)
+{
+ return boost::apply_visitor(*this, _expr.expression);
+}
+
bool ScopeFiller::operator()(Label const& _item)
{
if (!m_currentScope->registerLabel(_item.name))
@@ -71,10 +76,10 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
{
bool success = true;
vector<Scope::JuliaType> arguments;
- for (auto const& _argument: _funDef.arguments)
+ for (auto const& _argument: _funDef.parameters)
arguments.push_back(_argument.type);
vector<Scope::JuliaType> returns;
- for (auto const& _return: _funDef.returns)
+ for (auto const& _return: _funDef.returnVariables)
returns.push_back(_return.type);
if (!m_currentScope->registerFunction(_funDef.name, arguments, returns))
{
@@ -91,7 +96,7 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
varScope.superScope = m_currentScope;
m_currentScope = &varScope;
varScope.functionScope = true;
- for (auto const& var: _funDef.arguments + _funDef.returns)
+ for (auto const& var: _funDef.parameters + _funDef.returnVariables)
if (!registerVariable(var, _funDef.location, varScope))
success = false;
@@ -104,6 +109,11 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
return success;
}
+bool ScopeFiller::operator()(If const& _if)
+{
+ return (*this)(_if.body);
+}
+
bool ScopeFiller::operator()(Switch const& _switch)
{
bool success = true;
diff --git a/libsolidity/inlineasm/AsmScopeFiller.h b/libsolidity/inlineasm/AsmScopeFiller.h
index 80c03d2c..bb023f61 100644
--- a/libsolidity/inlineasm/AsmScopeFiller.h
+++ b/libsolidity/inlineasm/AsmScopeFiller.h
@@ -53,12 +53,14 @@ public:
bool operator()(assembly::Literal const&) { return true; }
bool operator()(assembly::Identifier const&) { return true; }
bool operator()(assembly::FunctionalInstruction const&) { return true; }
+ bool operator()(assembly::ExpressionStatement const& _expr);
bool operator()(assembly::Label const& _label);
bool operator()(assembly::StackAssignment const&) { return true; }
bool operator()(assembly::Assignment const&) { return true; }
bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
bool operator()(assembly::FunctionCall const&) { return true; }
+ bool operator()(assembly::If const& _if);
bool operator()(assembly::Switch const& _switch);
bool operator()(assembly::ForLoop const& _forLoop);
bool operator()(assembly::Block const& _block);
diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp
index 504ad92c..1b4bd270 100644
--- a/libsolidity/interface/AssemblyStack.cpp
+++ b/libsolidity/interface/AssemblyStack.cpp
@@ -38,6 +38,25 @@ using namespace std;
using namespace dev;
using namespace dev::solidity;
+namespace
+{
+assembly::AsmFlavour languageToAsmFlavour(AssemblyStack::Language _language)
+{
+ switch (_language)
+ {
+ case AssemblyStack::Language::Assembly:
+ return assembly::AsmFlavour::Loose;
+ case AssemblyStack::Language::StrictAssembly:
+ return assembly::AsmFlavour::Strict;
+ case AssemblyStack::Language::JULIA:
+ return assembly::AsmFlavour::IULIA;
+ }
+ solAssert(false, "");
+ return assembly::AsmFlavour::IULIA;
+}
+
+}
+
Scanner const& AssemblyStack::scanner() const
{
@@ -50,7 +69,7 @@ bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string
m_errors.clear();
m_analysisSuccessful = false;
m_scanner = make_shared<Scanner>(CharStream(_source), _sourceName);
- m_parserResult = assembly::Parser(m_errorReporter, m_language == Language::JULIA).parse(m_scanner);
+ m_parserResult = assembly::Parser(m_errorReporter, languageToAsmFlavour(m_language)).parse(m_scanner);
if (!m_errorReporter.errors().empty())
return false;
solAssert(m_parserResult, "");
@@ -72,7 +91,7 @@ bool AssemblyStack::analyze(assembly::Block const& _block, Scanner const* _scann
bool AssemblyStack::analyzeParsed()
{
m_analysisInfo = make_shared<assembly::AsmAnalysisInfo>();
- assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, m_language == Language::JULIA);
+ assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, languageToAsmFlavour(m_language));
m_analysisSuccessful = analyzer.analyze(*m_parserResult);
return m_analysisSuccessful;
}
diff --git a/libsolidity/interface/AssemblyStack.h b/libsolidity/interface/AssemblyStack.h
index 2ae596ed..6ae7e8d1 100644
--- a/libsolidity/interface/AssemblyStack.h
+++ b/libsolidity/interface/AssemblyStack.h
@@ -51,7 +51,7 @@ struct MachineAssemblyObject
class AssemblyStack
{
public:
- enum class Language { JULIA, Assembly };
+ enum class Language { JULIA, Assembly, StrictAssembly };
enum class Machine { EVM, EVM15, eWasm };
explicit AssemblyStack(Language _language = Language::Assembly):
diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp
index 5713256a..3b5e65e8 100644
--- a/libsolidity/interface/CompilerStack.cpp
+++ b/libsolidity/interface/CompilerStack.cpp
@@ -734,9 +734,12 @@ void CompilerStack::compileContract(
try
{
- Compiler cloneCompiler(m_optimize, m_optimizeRuns);
- cloneCompiler.compileClone(_contract, _compiledContracts);
- compiledContract.cloneObject = cloneCompiler.assembledObject();
+ if (!_contract.isLibrary())
+ {
+ Compiler cloneCompiler(m_optimize, m_optimizeRuns);
+ cloneCompiler.compileClone(_contract, _compiledContracts);
+ compiledContract.cloneObject = cloneCompiler.assembledObject();
+ }
}
catch (eth::AssemblyException const&)
{
diff --git a/libsolidity/interface/Exceptions.h b/libsolidity/interface/Exceptions.h
index 09301b10..7c66d572 100644
--- a/libsolidity/interface/Exceptions.h
+++ b/libsolidity/interface/Exceptions.h
@@ -109,6 +109,18 @@ public:
infos.push_back(std::make_pair(_errMsg, _sourceLocation));
return *this;
}
+ /// Limits the number of secondary source locations to 32 and appends a notice to the
+ /// error message.
+ void limitSize(std::string& _message)
+ {
+ size_t occurrences = infos.size();
+ if (occurrences > 32)
+ {
+ infos.resize(32);
+ _message += " Truncated from " + boost::lexical_cast<std::string>(occurrences) + " to the first 32 occurrences.";
+ }
+ }
+
std::vector<errorSourceLocationInfo> infos;
};
diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp
index 430739ac..84dedfb8 100644
--- a/libsolidity/interface/StandardCompiler.cpp
+++ b/libsolidity/interface/StandardCompiler.cpp
@@ -81,15 +81,15 @@ Json::Value formatErrorWithException(
else
message = _message;
+ Json::Value sourceLocation;
if (location && location->sourceName)
{
- Json::Value sourceLocation = Json::objectValue;
sourceLocation["file"] = *location->sourceName;
sourceLocation["start"] = location->start;
sourceLocation["end"] = location->end;
}
- return formatError(_warning, _type, _component, message, formattedMessage, location);
+ return formatError(_warning, _type, _component, message, formattedMessage, sourceLocation);
}
set<string> requestedContractNames(Json::Value const& _outputSelection)
@@ -131,6 +131,61 @@ StringMap createSourceList(Json::Value const& _input)
return sources;
}
+bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact)
+{
+ for (auto const& artifact: _outputSelection)
+ /// @TODO support sub-matching, e.g "evm" matches "evm.assembly"
+ if (artifact == "*" || artifact == _artifact)
+ return true;
+ return false;
+}
+
+///
+/// @a _outputSelection is a JSON object containining a two-level hashmap, where the first level is the filename,
+/// the second level is the contract name and the value is an array of artifact names to be requested for that contract.
+/// @a _file is the current file
+/// @a _contract is the current contract
+/// @a _artifact is the current artifact name
+///
+/// @returns true if the @a _outputSelection has a match for the requested target in the specific file / contract.
+///
+/// In @a _outputSelection the use of '*' as a wildcard is permitted.
+///
+/// @TODO optimise this. Perhaps flatten the structure upfront.
+///
+bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact)
+{
+ if (!_outputSelection.isObject())
+ return false;
+
+ for (auto const& file: { _file, string("*") })
+ if (_outputSelection.isMember(file) && _outputSelection[file].isObject())
+ {
+ /// For SourceUnit-level targets (such as AST) only allow empty name, otherwise
+ /// for Contract-level targets try both contract name and wildcard
+ vector<string> contracts{ _contract };
+ if (!_contract.empty())
+ contracts.push_back("*");
+ for (auto const& contract: contracts)
+ if (
+ _outputSelection[file].isMember(contract) &&
+ _outputSelection[file][contract].isArray() &&
+ isArtifactRequested(_outputSelection[file][contract], _artifact)
+ )
+ return true;
+ }
+
+ return false;
+}
+
+bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector<string> const& _artifacts)
+{
+ for (auto const& artifact: _artifacts)
+ if (isArtifactRequested(_outputSelection, _file, _contract, artifact))
+ return true;
+ return false;
+}
+
Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkReferences)
{
Json::Value ret(Json::objectValue);
@@ -138,7 +193,7 @@ Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkRefere
for (auto const& ref: linkReferences)
{
string const& fullname = ref.second;
- size_t colon = fullname.find(':');
+ size_t colon = fullname.rfind(':');
solAssert(colon != string::npos, "");
string file = fullname.substr(0, colon);
string name = fullname.substr(colon + 1);
@@ -181,7 +236,11 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
return formatFatalError("JSONError", "Only \"Solidity\" is supported as a language.");
Json::Value const& sources = _input["sources"];
- if (!sources)
+
+ if (!sources.isObject() && !sources.isNull())
+ return formatFatalError("JSONError", "\"sources\" is not a JSON object.");
+
+ if (sources.empty())
return formatFatalError("JSONError", "No input sources specified.");
Json::Value errors = Json::arrayValue;
@@ -396,43 +455,65 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
{
Json::Value sourceResult = Json::objectValue;
sourceResult["id"] = sourceIndex++;
- sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
- sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
+ if (isArtifactRequested(outputSelection, sourceName, "", "ast"))
+ sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
+ if (isArtifactRequested(outputSelection, sourceName, "", "legacyAST"))
+ sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
output["sources"][sourceName] = sourceResult;
}
Json::Value contractsOutput = Json::objectValue;
for (string const& contractName: compilationSuccess ? m_compilerStack.contractNames() : vector<string>())
{
- size_t colon = contractName.find(':');
+ size_t colon = contractName.rfind(':');
solAssert(colon != string::npos, "");
string file = contractName.substr(0, colon);
string name = contractName.substr(colon + 1);
// ABI, documentation and metadata
Json::Value contractData(Json::objectValue);
- contractData["abi"] = m_compilerStack.contractABI(contractName);
- contractData["metadata"] = m_compilerStack.metadata(contractName);
- contractData["userdoc"] = m_compilerStack.natspecUser(contractName);
- contractData["devdoc"] = m_compilerStack.natspecDev(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "abi"))
+ contractData["abi"] = m_compilerStack.contractABI(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "metadata"))
+ contractData["metadata"] = m_compilerStack.metadata(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "userdoc"))
+ contractData["userdoc"] = m_compilerStack.natspecUser(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "devdoc"))
+ contractData["devdoc"] = m_compilerStack.natspecDev(contractName);
// EVM
Json::Value evmData(Json::objectValue);
// @TODO: add ir
- evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input));
- evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input));
- evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName);
- evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName);
-
- evmData["bytecode"] = collectEVMObject(
- m_compilerStack.object(contractName),
- m_compilerStack.sourceMapping(contractName)
- );
-
- evmData["deployedBytecode"] = collectEVMObject(
- m_compilerStack.runtimeObject(contractName),
- m_compilerStack.runtimeSourceMapping(contractName)
- );
+ if (isArtifactRequested(outputSelection, file, name, "evm.assembly"))
+ evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input));
+ if (isArtifactRequested(outputSelection, file, name, "evm.legacyAssembly"))
+ evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input));
+ if (isArtifactRequested(outputSelection, file, name, "evm.methodIdentifiers"))
+ evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "evm.gasEstimates"))
+ evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName);
+
+ if (isArtifactRequested(
+ outputSelection,
+ file,
+ name,
+ { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" }
+ ))
+ evmData["bytecode"] = collectEVMObject(
+ m_compilerStack.object(contractName),
+ m_compilerStack.sourceMapping(contractName)
+ );
+
+ if (isArtifactRequested(
+ outputSelection,
+ file,
+ name,
+ { "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" }
+ ))
+ evmData["deployedBytecode"] = collectEVMObject(
+ m_compilerStack.runtimeObject(contractName),
+ m_compilerStack.runtimeSourceMapping(contractName)
+ );
contractData["evm"] = evmData;
diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp
index 821e81d2..05b877b5 100644
--- a/libsolidity/parsing/Parser.cpp
+++ b/libsolidity/parsing/Parser.cpp
@@ -644,15 +644,11 @@ ASTPointer<EventDefinition> Parser::parseEventDefinition()
expectToken(Token::Event);
ASTPointer<ASTString> name(expectIdentifierToken());
- ASTPointer<ParameterList> parameters;
- if (m_scanner->currentToken() == Token::LParen)
- {
- VarDeclParserOptions options;
- options.allowIndexed = true;
- parameters = parseParameterList(options);
- }
- else
- parameters = createEmptyParameterList();
+
+ VarDeclParserOptions options;
+ options.allowIndexed = true;
+ ASTPointer<ParameterList> parameters = parseParameterList(options);
+
bool anonymous = false;
if (m_scanner->currentToken() == Token::Anonymous)
{