aboutsummaryrefslogtreecommitdiffstats
path: root/libsolidity
diff options
context:
space:
mode:
Diffstat (limited to 'libsolidity')
-rw-r--r--libsolidity/CMakeLists.txt35
-rw-r--r--libsolidity/analysis/StaticAnalyzer.cpp44
-rw-r--r--libsolidity/analysis/StaticAnalyzer.h3
-rw-r--r--libsolidity/analysis/SyntaxChecker.cpp43
-rw-r--r--libsolidity/analysis/SyntaxChecker.h2
-rw-r--r--libsolidity/analysis/TypeChecker.cpp91
-rw-r--r--libsolidity/analysis/TypeChecker.h3
-rw-r--r--libsolidity/ast/AST.cpp11
-rw-r--r--libsolidity/ast/AST.h50
-rw-r--r--libsolidity/ast/ASTAnnotations.h3
-rw-r--r--libsolidity/ast/ASTEnums.h54
-rw-r--r--libsolidity/ast/ASTForward.h1
-rw-r--r--libsolidity/ast/ASTJsonConverter.cpp80
-rw-r--r--libsolidity/ast/ASTJsonConverter.h21
-rw-r--r--libsolidity/ast/ASTPrinter.cpp14
-rw-r--r--libsolidity/ast/ASTPrinter.h4
-rw-r--r--libsolidity/ast/ASTVisitor.h4
-rw-r--r--libsolidity/ast/AST_accept.h12
-rw-r--r--libsolidity/ast/ExperimentalFeatures.h53
-rw-r--r--libsolidity/ast/Types.cpp155
-rw-r--r--libsolidity/ast/Types.h27
-rw-r--r--libsolidity/codegen/ABIFunctions.cpp1074
-rw-r--r--libsolidity/codegen/ABIFunctions.h172
-rw-r--r--libsolidity/codegen/ArrayUtils.h2
-rw-r--r--libsolidity/codegen/Compiler.h6
-rw-r--r--libsolidity/codegen/CompilerContext.cpp5
-rw-r--r--libsolidity/codegen/CompilerContext.h15
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp35
-rw-r--r--libsolidity/codegen/CompilerUtils.h10
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp3
-rw-r--r--libsolidity/codegen/ExpressionCompiler.cpp5
-rw-r--r--libsolidity/codegen/ExpressionCompiler.h2
-rw-r--r--libsolidity/formal/SMTChecker.cpp588
-rw-r--r--libsolidity/formal/SMTChecker.h114
-rw-r--r--libsolidity/formal/SMTLib2Interface.cpp187
-rw-r--r--libsolidity/formal/SMTLib2Interface.h75
-rw-r--r--libsolidity/formal/SolverInterface.h178
-rw-r--r--libsolidity/formal/Z3Interface.cpp189
-rw-r--r--libsolidity/formal/Z3Interface.h65
-rw-r--r--libsolidity/inlineasm/AsmCodeGen.cpp4
-rw-r--r--libsolidity/inlineasm/AsmParser.cpp32
-rw-r--r--libsolidity/inlineasm/AsmParser.h4
-rw-r--r--libsolidity/inlineasm/AsmPrinter.cpp2
-rw-r--r--libsolidity/inlineasm/AsmPrinter.h2
-rw-r--r--libsolidity/inlineasm/AsmScope.cpp2
-rw-r--r--libsolidity/inlineasm/AsmScope.h2
-rw-r--r--libsolidity/interface/ABI.cpp7
-rw-r--r--libsolidity/interface/CompilerStack.cpp101
-rw-r--r--libsolidity/interface/CompilerStack.h26
-rw-r--r--libsolidity/interface/ErrorReporter.h6
-rw-r--r--libsolidity/interface/ReadFile.h8
-rw-r--r--libsolidity/interface/StandardCompiler.cpp12
-rw-r--r--libsolidity/interface/StandardCompiler.h4
-rw-r--r--libsolidity/parsing/Parser.cpp121
-rw-r--r--libsolidity/parsing/Parser.h6
-rw-r--r--libsolidity/parsing/ParserBase.cpp13
-rw-r--r--libsolidity/parsing/ParserBase.h22
-rw-r--r--libsolidity/parsing/Scanner.h10
-rw-r--r--libsolidity/parsing/Token.h7
59 files changed, 3453 insertions, 373 deletions
diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt
index 2342f0f9..f7c1a390 100644
--- a/libsolidity/CMakeLists.txt
+++ b/libsolidity/CMakeLists.txt
@@ -1,23 +1,20 @@
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB")
-
-aux_source_directory(analysis SRC_LIST)
-aux_source_directory(ast SRC_LIST)
-aux_source_directory(codegen SRC_LIST)
-aux_source_directory(formal SRC_LIST)
-aux_source_directory(interface SRC_LIST)
-aux_source_directory(parsing SRC_LIST)
-aux_source_directory(inlineasm SRC_LIST)
# Until we have a clear separation, libjulia has to be included here
-aux_source_directory(../libjulia SRC_LIST)
-
-set(EXECUTABLE solidity)
-
-file(GLOB HEADERS "*/*.h" "../libjulia/backends/evm/*")
-
-include_directories(BEFORE ..)
-add_library(${EXECUTABLE} ${SRC_LIST} ${HEADERS})
+file(GLOB_RECURSE sources "*.cpp" "../libjulia/*.cpp")
+file(GLOB_RECURSE headers "*.h" "../libjulia/*.h")
-eth_use(${EXECUTABLE} REQUIRED Dev::soldevcore Solidity::solevmasm)
+find_package(Z3 QUIET)
+if (${Z3_FOUND})
+ include_directories(${Z3_INCLUDE_DIR})
+ add_definitions(-DHAVE_Z3)
+ message("Z3 SMT solver FOUND.")
+else()
+ message("Z3 SMT solver NOT found.")
+ list(REMOVE_ITEM sources "${CMAKE_CURRENT_SOURCE_DIR}/formal/Z3Interface.cpp")
+endif()
-install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib )
+add_library(solidity ${sources} ${headers})
+target_link_libraries(solidity PUBLIC evmasm devcore)
+if (${Z3_FOUND})
+ target_link_libraries(solidity PUBLIC ${Z3_LIBRARY})
+endif() \ No newline at end of file
diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp
index 46477e1e..2f130414 100644
--- a/libsolidity/analysis/StaticAnalyzer.cpp
+++ b/libsolidity/analysis/StaticAnalyzer.cpp
@@ -57,6 +57,8 @@ bool StaticAnalyzer::visit(FunctionDefinition const& _function)
solAssert(m_localVarUseCount.empty(), "");
m_nonPayablePublic = _function.isPublic() && !_function.isPayable();
m_constructor = _function.isConstructor();
+ if (_function.stateMutability() == StateMutability::Pure)
+ m_errorReporter.warning(_function.location(), "Function is marked pure. Be careful, pureness is not enforced yet.");
return true;
}
@@ -92,6 +94,17 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
// This is not a no-op, the entry might pre-exist.
m_localVarUseCount[&_variable] += 0;
}
+ else if (_variable.isStateVariable())
+ {
+ set<StructDefinition const*> structsSeen;
+ if (structureSizeEstimate(*_variable.type(), structsSeen) >= bigint(1) << 64)
+ m_errorReporter.warning(
+ _variable.location(),
+ "Variable covers a large part of storage and thus makes collisions likely. "
+ "Either use mappings or dynamic arrays and allow their size to be increased only "
+ "in small quantities per transaction."
+ );
+ }
return true;
}
@@ -160,3 +173,34 @@ bool StaticAnalyzer::visit(InlineAssembly const& _inlineAssembly)
return true;
}
+
+bigint StaticAnalyzer::structureSizeEstimate(Type const& _type, set<StructDefinition const*>& _structsSeen)
+{
+ switch (_type.category())
+ {
+ case Type::Category::Array:
+ {
+ auto const& t = dynamic_cast<ArrayType const&>(_type);
+ return structureSizeEstimate(*t.baseType(), _structsSeen) * (t.isDynamicallySized() ? 1 : t.length());
+ }
+ case Type::Category::Struct:
+ {
+ auto const& t = dynamic_cast<StructType const&>(_type);
+ bigint size = 1;
+ if (!_structsSeen.count(&t.structDefinition()))
+ {
+ _structsSeen.insert(&t.structDefinition());
+ for (auto const& m: t.members(nullptr))
+ size += structureSizeEstimate(*m.type, _structsSeen);
+ }
+ return size;
+ }
+ case Type::Category::Mapping:
+ {
+ return structureSizeEstimate(*dynamic_cast<MappingType const&>(_type).valueType(), _structsSeen);
+ }
+ default:
+ break;
+ }
+ return bigint(1);
+}
diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h
index 21a487df..a3080b42 100644
--- a/libsolidity/analysis/StaticAnalyzer.h
+++ b/libsolidity/analysis/StaticAnalyzer.h
@@ -65,6 +65,9 @@ private:
virtual bool visit(MemberAccess const& _memberAccess) override;
virtual bool visit(InlineAssembly const& _inlineAssembly) override;
+ /// @returns the size of this type in storage, including all sub-types.
+ static bigint structureSizeEstimate(Type const& _type, std::set<StructDefinition const*>& _structsSeen);
+
ErrorReporter& m_errorReporter;
/// Flag that indicates whether the current contract definition is a library.
diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp
index bde0e616..d2571cd3 100644
--- a/libsolidity/analysis/SyntaxChecker.cpp
+++ b/libsolidity/analysis/SyntaxChecker.cpp
@@ -18,6 +18,7 @@
#include <libsolidity/analysis/SyntaxChecker.h>
#include <memory>
#include <libsolidity/ast/AST.h>
+#include <libsolidity/ast/ExperimentalFeatures.h>
#include <libsolidity/analysis/SemVerHandler.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/interface/Version.h>
@@ -33,9 +34,10 @@ bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot)
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
-bool SyntaxChecker::visit(SourceUnit const&)
+bool SyntaxChecker::visit(SourceUnit const& _sourceUnit)
{
m_versionPragmaFound = false;
+ m_sourceUnit = &_sourceUnit;
return true;
}
@@ -57,15 +59,46 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit)
m_errorReporter.warning(_sourceUnit.location(), errorString);
}
+ m_sourceUnit = nullptr;
}
bool SyntaxChecker::visit(PragmaDirective const& _pragma)
{
solAssert(!_pragma.tokens().empty(), "");
solAssert(_pragma.tokens().size() == _pragma.literals().size(), "");
- if (_pragma.tokens()[0] != Token::Identifier || _pragma.literals()[0] != "solidity")
- m_errorReporter.syntaxError(_pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\"");
- else
+ if (_pragma.tokens()[0] != Token::Identifier)
+ m_errorReporter.syntaxError(_pragma.location(), "Invalid pragma \"" + _pragma.literals()[0] + "\"");
+ else if (_pragma.literals()[0] == "experimental")
+ {
+ solAssert(m_sourceUnit, "");
+ vector<string> literals(_pragma.literals().begin() + 1, _pragma.literals().end());
+ if (literals.size() == 0)
+ m_errorReporter.syntaxError(
+ _pragma.location(),
+ "Experimental feature name is missing."
+ );
+ else if (literals.size() > 1)
+ m_errorReporter.syntaxError(
+ _pragma.location(),
+ "Stray arguments."
+ );
+ else
+ {
+ string const literal = literals[0];
+ if (literal.empty())
+ m_errorReporter.syntaxError(_pragma.location(), "Empty experimental feature name is invalid.");
+ else if (!ExperimentalFeatureNames.count(literal))
+ m_errorReporter.syntaxError(_pragma.location(), "Unsupported experimental feature name.");
+ else if (m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeatureNames.at(literal)))
+ m_errorReporter.syntaxError(_pragma.location(), "Duplicate experimental feature name.");
+ else
+ {
+ m_sourceUnit->annotation().experimentalFeatures.insert(ExperimentalFeatureNames.at(literal));
+ m_errorReporter.warning(_pragma.location(), "Experimental features are turned on. Do not use experimental features on live deployments.");
+ }
+ }
+ }
+ else if (_pragma.literals()[0] == "solidity")
{
vector<Token::Value> tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end());
vector<string> literals(_pragma.literals().begin() + 1, _pragma.literals().end());
@@ -81,6 +114,8 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma)
);
m_versionPragmaFound = true;
}
+ else
+ m_errorReporter.syntaxError(_pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\"");
return true;
}
diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h
index fb5cc6d7..fa34bab3 100644
--- a/libsolidity/analysis/SyntaxChecker.h
+++ b/libsolidity/analysis/SyntaxChecker.h
@@ -77,6 +77,8 @@ private:
bool m_versionPragmaFound = false;
int m_inLoopDepth = 0;
+
+ SourceUnit const* m_sourceUnit = nullptr;
};
}
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index 6852c13d..99f3c64c 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -84,8 +84,13 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
{
if (!function->returnParameters().empty())
m_errorReporter.typeError(function->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
- if (function->isDeclaredConst())
- m_errorReporter.typeError(function->location(), "Constructor cannot be defined as constant.");
+ if (function->stateMutability() != StateMutability::NonPayable && function->stateMutability() != StateMutability::Payable)
+ m_errorReporter.typeError(
+ function->location(),
+ "Constructor must be payable or non-payable, but is \"" +
+ stateMutabilityToString(function->stateMutability()) +
+ "\"."
+ );
if (function->visibility() != FunctionDefinition::Visibility::Public && function->visibility() != FunctionDefinition::Visibility::Internal)
m_errorReporter.typeError(function->location(), "Constructor must be public or internal.");
}
@@ -104,8 +109,13 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
fallbackFunction = function;
if (_contract.isLibrary())
m_errorReporter.typeError(fallbackFunction->location(), "Libraries cannot have fallback functions.");
- if (fallbackFunction->isDeclaredConst())
- m_errorReporter.typeError(fallbackFunction->location(), "Fallback function cannot be declared constant.");
+ if (function->stateMutability() != StateMutability::NonPayable && function->stateMutability() != StateMutability::Payable)
+ m_errorReporter.typeError(
+ function->location(),
+ "Fallback function must be payable or non-payable, but is \"" +
+ stateMutabilityToString(function->stateMutability()) +
+ "\"."
+ );
if (!fallbackFunction->parameters().empty())
m_errorReporter.typeError(fallbackFunction->parameterList().location(), "Fallback function cannot take parameters.");
if (!fallbackFunction->returnParameters().empty())
@@ -277,21 +287,10 @@ void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contr
string const& name = function->name();
if (modifiers.count(name))
m_errorReporter.typeError(modifiers[name]->location(), "Override changes function to modifier.");
- FunctionType functionType(*function);
- // function should not change the return type
+
for (FunctionDefinition const* overriding: functions[name])
- {
- FunctionType overridingType(*overriding);
- if (!overridingType.hasEqualArgumentTypes(functionType))
- continue;
- if (
- overriding->visibility() != function->visibility() ||
- overriding->isDeclaredConst() != function->isDeclaredConst() ||
- overriding->isPayable() != function->isPayable() ||
- overridingType != functionType
- )
- m_errorReporter.typeError(overriding->location(), "Override changes extended function signature.");
- }
+ checkFunctionOverride(*overriding, *function);
+
functions[name].push_back(function);
}
for (ModifierDefinition const* modifier: contract->functionModifiers())
@@ -308,6 +307,41 @@ void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contr
}
}
+void TypeChecker::checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super)
+{
+ FunctionType functionType(function);
+ FunctionType superType(super);
+
+ if (!functionType.hasEqualArgumentTypes(superType))
+ return;
+
+ if (function.visibility() != super.visibility())
+ overrideError(function, super, "Overriding function visibility differs.");
+
+ else if (function.stateMutability() != super.stateMutability())
+ overrideError(
+ function,
+ super,
+ "Overriding function changes state mutability from \"" +
+ stateMutabilityToString(super.stateMutability()) +
+ "\" to \"" +
+ stateMutabilityToString(function.stateMutability()) +
+ "\"."
+ );
+
+ else if (functionType != superType)
+ overrideError(function, super, "Overriding function return types differ.");
+}
+
+void TypeChecker::overrideError(FunctionDefinition const& function, FunctionDefinition const& super, string message)
+{
+ m_errorReporter.typeError(
+ function.location(),
+ SecondarySourceLocation().append("Overriden function is here:", super.location()),
+ message
+ );
+}
+
void TypeChecker::checkContractExternalTypeClashes(ContractDefinition const& _contract)
{
map<string, vector<pair<Declaration const*, FunctionTypePointer>>> externalDeclarations;
@@ -396,7 +430,11 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
m_errorReporter.typeError(_inheritance.location(), "Libraries cannot be inherited from.");
auto const& arguments = _inheritance.arguments();
- TypePointers parameterTypes = ContractType(*base).newExpressionType()->parameterTypes();
+ TypePointers parameterTypes;
+ if (base->contractKind() != ContractDefinition::ContractKind::Interface)
+ // Interfaces do not have constructors, so there are zero parameters.
+ parameterTypes = ContractType(*base).newExpressionType()->parameterTypes();
+
if (!arguments.empty() && parameterTypes.size() != arguments.size())
{
m_errorReporter.typeError(
@@ -429,7 +467,7 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
_usingFor.libraryName().annotation().referencedDeclaration
);
if (!library || !library->isLibrary())
- m_errorReporter.typeError(_usingFor.libraryName().location(), "Library name expected.");
+ m_errorReporter.fatalTypeError(_usingFor.libraryName().location(), "Library name expected.");
}
bool TypeChecker::visit(StructDefinition const& _struct)
@@ -475,8 +513,6 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
m_errorReporter.typeError(_function.location(), "Library functions cannot be payable.");
if (!_function.isConstructor() && !_function.isFallback() && !_function.isPartOfExternalInterface())
m_errorReporter.typeError(_function.location(), "Internal functions cannot be payable.");
- if (_function.isDeclaredConst())
- m_errorReporter.typeError(_function.location(), "Functions cannot be constant and payable at the same time.");
}
for (ASTPointer<VariableDeclaration> const& var: _function.parameters() + _function.returnParameters())
{
@@ -514,6 +550,9 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
if (_function.isConstructor())
m_errorReporter.typeError(_function.location(), "Constructor cannot be defined in interfaces.");
}
+ else if (m_scope->contractKind() == ContractDefinition::ContractKind::Library)
+ if (_function.isConstructor())
+ m_errorReporter.typeError(_function.location(), "Constructor cannot be defined in libraries.");
if (_function.isImplemented())
_function.body().accept(*this);
else if (_function.isConstructor())
@@ -1282,8 +1321,9 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
_operation.leftExpression().annotation().isPure &&
_operation.rightExpression().annotation().isPure;
- if (_operation.getOperator() == Token::Exp)
+ if (_operation.getOperator() == Token::Exp || _operation.getOperator() == Token::SHL)
{
+ string operation = _operation.getOperator() == Token::Exp ? "exponentiation" : "shift";
if (
leftType->category() == Type::Category::RationalNumber &&
rightType->category() != Type::Category::RationalNumber
@@ -1297,7 +1337,7 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
))
m_errorReporter.warning(
_operation.location(),
- "Result of exponentiation has type " + commonType->toString() + " and thus "
+ "Result of " + operation + " has type " + commonType->toString() + " and thus "
"might overflow. Silence this warning by converting the literal to the "
"expected type."
);
@@ -1518,6 +1558,8 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
if (!contract)
m_errorReporter.fatalTypeError(_newExpression.location(), "Identifier is not a contract.");
+ if (contract->contractKind() == ContractDefinition::ContractKind::Interface)
+ m_errorReporter.fatalTypeError(_newExpression.location(), "Cannot instantiate an interface.");
if (!contract->annotation().unimplementedFunctions.empty())
m_errorReporter.typeError(
_newExpression.location(),
@@ -1949,4 +1991,3 @@ void TypeChecker::requireLValue(Expression const& _expression)
else if (!_expression.annotation().isLValue)
m_errorReporter.typeError(_expression.location(), "Expression has to be an lvalue.");
}
-
diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h
index ee43d13a..f2e13765 100644
--- a/libsolidity/analysis/TypeChecker.h
+++ b/libsolidity/analysis/TypeChecker.h
@@ -62,6 +62,9 @@ private:
/// arguments and that there is at most one constructor.
void checkContractDuplicateFunctions(ContractDefinition const& _contract);
void checkContractIllegalOverrides(ContractDefinition const& _contract);
+ /// Reports a type error with an appropiate message if overriden function signature differs.
+ void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super);
+ void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message);
void checkContractAbstractFunctions(ContractDefinition const& _contract);
void checkContractAbstractConstructors(ContractDefinition const& _contract);
/// Checks that different functions with external visibility end up having different
diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp
index 1d68231e..7f4dea0e 100644
--- a/libsolidity/ast/AST.cpp
+++ b/libsolidity/ast/AST.cpp
@@ -22,6 +22,7 @@
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h>
+#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/ast/AST_accept.h>
#include <libdevcore/SHA3.h>
@@ -188,7 +189,6 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::inter
{
if (!m_interfaceFunctionList)
{
- set<string> functionsSeen;
set<string> signaturesSeen;
m_interfaceFunctionList.reset(new vector<pair<FixedHash<4>, FunctionTypePointer>>());
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
@@ -371,6 +371,15 @@ string FunctionDefinition::externalSignature() const
return FunctionType(*this).externalSignature();
}
+string FunctionDefinition::fullyQualifiedName() const
+{
+ auto const* contract = dynamic_cast<ContractDefinition const*>(scope());
+ solAssert(contract, "Enclosing scope of function definition was not set.");
+
+ auto fname = name().empty() ? "<fallback>" : name();
+ return sourceUnitName() + ":" + contract->name() + "." + fname;
+}
+
FunctionDefinitionAnnotation& FunctionDefinition::annotation() const
{
if (!m_annotation)
diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h
index 3e97286b..4592a190 100644
--- a/libsolidity/ast/AST.h
+++ b/libsolidity/ast/AST.h
@@ -28,6 +28,7 @@
#include <libsolidity/ast/Types.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/ast/ASTAnnotations.h>
+#include <libsolidity/ast/ASTEnums.h>
#include <libevmasm/SourceLocation.h>
#include <libevmasm/Instruction.h>
@@ -152,6 +153,24 @@ public:
/// Visibility ordered from restricted to unrestricted.
enum class Visibility { Default, Private, Internal, Public, External };
+ static std::string visibilityToString(Declaration::Visibility _visibility)
+ {
+ switch(_visibility)
+ {
+ case Declaration::Visibility::Public:
+ return "public";
+ case Declaration::Visibility::Internal:
+ return "internal";
+ case Declaration::Visibility::Private:
+ return "private";
+ case Declaration::Visibility::External:
+ return "external";
+ default:
+ solAssert(false, "Invalid visibility specifier.");
+ }
+ return std::string();
+ }
+
Declaration(
SourceLocation const& _location,
ASTPointer<ASTString> const& _name,
@@ -566,21 +585,19 @@ public:
SourceLocation const& _location,
ASTPointer<ASTString> const& _name,
Declaration::Visibility _visibility,
+ StateMutability _stateMutability,
bool _isConstructor,
ASTPointer<ASTString> const& _documentation,
ASTPointer<ParameterList> const& _parameters,
- bool _isDeclaredConst,
std::vector<ASTPointer<ModifierInvocation>> const& _modifiers,
ASTPointer<ParameterList> const& _returnParameters,
- bool _isPayable,
ASTPointer<Block> const& _body
):
CallableDeclaration(_location, _name, _visibility, _parameters, _returnParameters),
Documented(_documentation),
ImplementationOptional(_body != nullptr),
+ m_stateMutability(_stateMutability),
m_isConstructor(_isConstructor),
- m_isDeclaredConst(_isDeclaredConst),
- m_isPayable(_isPayable),
m_functionModifiers(_modifiers),
m_body(_body)
{}
@@ -588,13 +605,14 @@ public:
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
+ StateMutability stateMutability() const { return m_stateMutability; }
bool isConstructor() const { return m_isConstructor; }
bool isFallback() const { return name().empty(); }
- bool isDeclaredConst() const { return m_isDeclaredConst; }
- bool isPayable() const { return m_isPayable; }
+ bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; }
std::vector<ASTPointer<VariableDeclaration>> const& returnParameters() const { return m_returnParameters->parameters(); }
Block const& body() const { solAssert(m_body, ""); return *m_body; }
+ std::string fullyQualifiedName() const;
virtual bool isVisibleInContract() const override
{
return Declaration::isVisibleInContract() && !isConstructor() && !isFallback();
@@ -615,9 +633,8 @@ public:
virtual FunctionDefinitionAnnotation& annotation() const override;
private:
+ StateMutability m_stateMutability;
bool m_isConstructor;
- bool m_isDeclaredConst;
- bool m_isPayable;
std::vector<ASTPointer<ModifierInvocation>> m_functionModifiers;
ASTPointer<Block> m_body;
};
@@ -819,11 +836,10 @@ private:
*/
class TypeName: public ASTNode
{
-public:
+protected:
explicit TypeName(SourceLocation const& _location): ASTNode(_location) {}
- virtual void accept(ASTVisitor& _visitor) override;
- virtual void accept(ASTConstVisitor& _visitor) const override;
+public:
virtual TypeNameAnnotation& annotation() const override;
};
@@ -877,11 +893,10 @@ public:
ASTPointer<ParameterList> const& _parameterTypes,
ASTPointer<ParameterList> const& _returnTypes,
Declaration::Visibility _visibility,
- bool _isDeclaredConst,
- bool _isPayable
+ StateMutability _stateMutability
):
TypeName(_location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes),
- m_visibility(_visibility), m_isDeclaredConst(_isDeclaredConst), m_isPayable(_isPayable)
+ m_visibility(_visibility), m_stateMutability(_stateMutability)
{}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
@@ -895,15 +910,14 @@ public:
{
return m_visibility == Declaration::Visibility::Default ? Declaration::Visibility::Internal : m_visibility;
}
- bool isDeclaredConst() const { return m_isDeclaredConst; }
- bool isPayable() const { return m_isPayable; }
+ StateMutability stateMutability() const { return m_stateMutability; }
+ bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
private:
ASTPointer<ParameterList> m_parameterTypes;
ASTPointer<ParameterList> m_returnTypes;
Declaration::Visibility m_visibility;
- bool m_isDeclaredConst;
- bool m_isPayable;
+ StateMutability m_stateMutability;
};
/**
diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h
index f757f03c..fd9efb4d 100644
--- a/libsolidity/ast/ASTAnnotations.h
+++ b/libsolidity/ast/ASTAnnotations.h
@@ -23,6 +23,7 @@
#pragma once
#include <libsolidity/ast/ASTForward.h>
+#include <libsolidity/ast/ExperimentalFeatures.h>
#include <map>
#include <memory>
@@ -61,6 +62,8 @@ struct SourceUnitAnnotation: ASTAnnotation
std::string path;
/// The exported symbols (all global symbols).
std::map<ASTString, std::vector<Declaration const*>> exportedSymbols;
+ /// Experimental features.
+ std::set<ExperimentalFeature> experimentalFeatures;
};
struct ImportAnnotation: ASTAnnotation
diff --git a/libsolidity/ast/ASTEnums.h b/libsolidity/ast/ASTEnums.h
new file mode 100644
index 00000000..5ba21907
--- /dev/null
+++ b/libsolidity/ast/ASTEnums.h
@@ -0,0 +1,54 @@
+/*
+ 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/>.
+*/
+/**
+ * @date 2017
+ * Enums for AST classes.
+ */
+
+#pragma once
+
+#include <libsolidity/interface/Exceptions.h>
+
+#include <string>
+
+namespace dev
+{
+namespace solidity
+{
+
+// How a function can mutate the EVM state.
+enum class StateMutability { Pure, View, NonPayable, Payable };
+
+inline std::string stateMutabilityToString(StateMutability const& _stateMutability)
+{
+ switch(_stateMutability)
+ {
+ case StateMutability::Pure:
+ return "pure";
+ case StateMutability::View:
+ return "view";
+ case StateMutability::NonPayable:
+ return "nonpayable";
+ case StateMutability::Payable:
+ return "payable";
+ default:
+ solAssert(false, "Unknown state mutability.");
+ }
+}
+
+}
+}
diff --git a/libsolidity/ast/ASTForward.h b/libsolidity/ast/ASTForward.h
index cfeeaa58..15735368 100644
--- a/libsolidity/ast/ASTForward.h
+++ b/libsolidity/ast/ASTForward.h
@@ -95,6 +95,5 @@ using ASTPointer = std::shared_ptr<T>;
using ASTString = std::string;
-
}
}
diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp
index e4a602cb..afc53bfe 100644
--- a/libsolidity/ast/ASTJsonConverter.cpp
+++ b/libsolidity/ast/ASTJsonConverter.cpp
@@ -15,8 +15,7 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
- * @author Lefteris <lefteris@ethdev.com>
- * @date 2015
+ * @date 2017
* Converts the AST into json format
*/
@@ -81,28 +80,30 @@ void ASTJsonConverter::setJsonNode(
(_nodeType == "InlineAssembly") ||
(_nodeType == "Throw")
)
- {
- Json::Value children(Json::arrayValue);
- m_currentValue["children"] = children;
- }
+ m_currentValue["children"] = Json::arrayValue;
for (auto& e: _attributes)
{
- if (
- (!e.second.isNull()) &&
- (
- (e.second.isObject() && e.second.isMember("name")) ||
- (e.second.isArray() && e.second[0].isObject() && e.second[0].isMember("name")) ||
- (e.first == "declarations") // (in the case (_,x)= ... there's a nullpointer at [0]
- )
- )
+ if ((!e.second.isNull()) && (
+ (e.second.isObject() && e.second.isMember("name")) ||
+ (e.second.isArray() && e.second[0].isObject() && e.second[0].isMember("name")) ||
+ (e.first == "declarations") // (in the case (_,x)= ... there's a nullpointer at [0]
+ ))
{
if (e.second.isObject())
- m_currentValue["children"].append(std::move(e.second));
+ {
+ if (!m_currentValue["children"].isArray())
+ m_currentValue["children"] = Json::arrayValue;
+ appendMove(m_currentValue["children"], std::move(e.second));
+ }
if (e.second.isArray())
for (auto& child: e.second)
if (!child.isNull())
- m_currentValue["children"].append(std::move(child));
+ {
+ if (!m_currentValue["children"].isArray())
+ m_currentValue["children"] = Json::arrayValue;
+ appendMove(m_currentValue["children"], std::move(child));
+ }
}
else
{
@@ -147,7 +148,7 @@ Json::Value ASTJsonConverter::typePointerToJson(std::shared_ptr<std::vector<Type
{
Json::Value arguments(Json::arrayValue);
for (auto const& tp: *_tps)
- arguments.append(typePointerToJson(tp));
+ appendMove(arguments, typePointerToJson(tp));
return arguments;
}
else
@@ -186,7 +187,7 @@ void ASTJsonConverter::print(ostream& _stream, ASTNode const& _node)
_stream << toJson(_node);
}
-Json::Value ASTJsonConverter::toJson(ASTNode const& _node)
+Json::Value&& ASTJsonConverter::toJson(ASTNode const& _node)
{
_node.accept(*this);
return std::move(m_currentValue);
@@ -285,7 +286,7 @@ bool ASTJsonConverter::visit(StructDefinition const& _node)
{
setJsonNode(_node, "StructDefinition", {
make_pair("name", _node.name()),
- make_pair("visibility", visibility(_node.visibility())),
+ make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("canonicalName", _node.annotation().canonicalName),
make_pair("members", toJson(_node.members())),
make_pair("scope", idOrNull(_node.scope()))
@@ -323,9 +324,11 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node)
{
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
- make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.isDeclaredConst()),
+ // FIXME: remove with next breaking release
+ make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.stateMutability() <= StateMutability::View),
make_pair("payable", _node.isPayable()),
- make_pair("visibility", visibility(_node.visibility())),
+ make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())),
+ make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("parameters", toJson(_node.parameterList())),
make_pair("isConstructor", _node.isConstructor()),
make_pair("returnParameters", toJson(*_node.returnParameterList())),
@@ -346,7 +349,7 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node)
make_pair("constant", _node.isConstant()),
make_pair("stateVariable", _node.isStateVariable()),
make_pair("storageLocation", location(_node.referenceLocation())),
- make_pair("visibility", visibility(_node.visibility())),
+ make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("value", _node.value() ? toJson(*_node.value()) : Json::nullValue),
make_pair("scope", idOrNull(_node.scope())),
make_pair("typeDescriptions", typePointerToJson(_node.annotation().type))
@@ -361,7 +364,7 @@ bool ASTJsonConverter::visit(ModifierDefinition const& _node)
{
setJsonNode(_node, "ModifierDefinition", {
make_pair("name", _node.name()),
- make_pair("visibility", visibility(_node.visibility())),
+ make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("parameters", toJson(_node.parameterList())),
make_pair("body", toJson(_node.body()))
});
@@ -377,12 +380,6 @@ bool ASTJsonConverter::visit(ModifierInvocation const& _node)
return false;
}
-bool ASTJsonConverter::visit(TypeName const&)
-{
- solAssert(false, "AST node of abstract type used.");
- return false;
-}
-
bool ASTJsonConverter::visit(EventDefinition const& _node)
{
m_inEvent = true;
@@ -418,8 +415,10 @@ bool ASTJsonConverter::visit(FunctionTypeName const& _node)
{
setJsonNode(_node, "FunctionTypeName", {
make_pair("payable", _node.isPayable()),
- make_pair("visibility", visibility(_node.visibility())),
- make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.isDeclaredConst()),
+ make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
+ make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())),
+ // FIXME: remove with next breaking release
+ make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.stateMutability() <= StateMutability::View),
make_pair("parameterTypes", toJson(*_node.parameterTypeList())),
make_pair("returnParameterTypes", toJson(*_node.returnParameterTypeList())),
make_pair("typeDescriptions", typePointerToJson(_node.annotation().type))
@@ -545,7 +544,7 @@ bool ASTJsonConverter::visit(VariableDeclarationStatement const& _node)
{
Json::Value varDecs(Json::arrayValue);
for (auto const& v: _node.annotation().assignments)
- varDecs.append(idOrNull(v));
+ appendMove(varDecs, idOrNull(v));
setJsonNode(_node, "VariableDeclarationStatement", {
make_pair("assignments", std::move(varDecs)),
make_pair("declarations", toJson(_node.declarations())),
@@ -730,23 +729,6 @@ void ASTJsonConverter::endVisit(EventDefinition const&)
m_inEvent = false;
}
-string ASTJsonConverter::visibility(Declaration::Visibility const& _visibility)
-{
- switch (_visibility)
- {
- case Declaration::Visibility::Private:
- return "private";
- case Declaration::Visibility::Internal:
- return "internal";
- case Declaration::Visibility::Public:
- return "public";
- case Declaration::Visibility::External:
- return "external";
- default:
- solAssert(false, "Unknown declaration visibility.");
- }
-}
-
string ASTJsonConverter::location(VariableDeclaration::Location _location)
{
switch (_location)
diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h
index 27114c2a..60c660c1 100644
--- a/libsolidity/ast/ASTJsonConverter.h
+++ b/libsolidity/ast/ASTJsonConverter.h
@@ -49,13 +49,16 @@ public:
);
/// Output the json representation of the AST to _stream.
void print(std::ostream& _stream, ASTNode const& _node);
- Json::Value toJson(ASTNode const& _node);
+ Json::Value&& toJson(ASTNode const& _node);
template <class T>
Json::Value toJson(std::vector<ASTPointer<T>> const& _nodes)
{
Json::Value ret(Json::arrayValue);
for (auto const& n: _nodes)
- ret.append(n ? toJson(*n) : Json::nullValue);
+ if (n)
+ appendMove(ret, toJson(*n));
+ else
+ ret.append(Json::nullValue);
return ret;
}
bool visit(SourceUnit const& _node) override;
@@ -73,7 +76,6 @@ public:
bool visit(ModifierDefinition const& _node) override;
bool visit(ModifierInvocation const& _node) override;
bool visit(EventDefinition const& _node) override;
- bool visit(TypeName const& _node) override;
bool visit(ElementaryTypeName const& _node) override;
bool visit(UserDefinedTypeName const& _node) override;
bool visit(FunctionTypeName const& _node) override;
@@ -119,7 +121,7 @@ private:
);
std::string sourceLocationToString(SourceLocation const& _location) const;
std::string namePathToString(std::vector<ASTString> const& _namePath) const;
- Json::Value idOrNull(ASTNode const* _pt)
+ static Json::Value idOrNull(ASTNode const* _pt)
{
return _pt ? Json::Value(nodeId(*_pt)) : Json::nullValue;
}
@@ -128,19 +130,18 @@ private:
return _node ? toJson(*_node) : Json::nullValue;
}
Json::Value inlineAssemblyIdentifierToJson(std::pair<assembly::Identifier const* , InlineAssemblyAnnotation::ExternalIdentifierInfo> _info);
- std::string visibility(Declaration::Visibility const& _visibility);
std::string location(VariableDeclaration::Location _location);
std::string contractKind(ContractDefinition::ContractKind _kind);
std::string functionCallKind(FunctionCallKind _kind);
std::string literalTokenKind(Token::Value _token);
std::string type(Expression const& _expression);
std::string type(VariableDeclaration const& _varDecl);
- int nodeId(ASTNode const& _node)
+ static int nodeId(ASTNode const& _node)
{
return _node.id();
}
template<class Container>
- Json::Value getContainerIds(Container const& container)
+ static Json::Value getContainerIds(Container const& container)
{
Json::Value tmp(Json::arrayValue);
for (auto const& element: container)
@@ -156,6 +157,12 @@ private:
std::vector<std::pair<std::string, Json::Value>> &_attributes,
ExpressionAnnotation const& _annotation
);
+ static void appendMove(Json::Value& _array, Json::Value&& _value)
+ {
+ solAssert(_array.isArray(), "");
+ _array.append(std::move(_value));
+ }
+
bool m_legacy = false; ///< if true, use legacy format
bool m_inEvent = false; ///< whether we are currently inside an event or not
Json::Value m_currentValue;
diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp
index 23eb3b64..392179ef 100644
--- a/libsolidity/ast/ASTPrinter.cpp
+++ b/libsolidity/ast/ASTPrinter.cpp
@@ -105,7 +105,7 @@ bool ASTPrinter::visit(FunctionDefinition const& _node)
{
writeLine("FunctionDefinition \"" + _node.name() + "\"" +
(_node.isPublic() ? " - public" : "") +
- (_node.isDeclaredConst() ? " - const" : ""));
+ (_node.stateMutability() == StateMutability::View ? " - const" : ""));
printSourcePart(_node);
return goDeeper();
}
@@ -143,13 +143,6 @@ bool ASTPrinter::visit(EventDefinition const& _node)
return goDeeper();
}
-bool ASTPrinter::visit(TypeName const& _node)
-{
- writeLine("TypeName");
- printSourcePart(_node);
- return goDeeper();
-}
-
bool ASTPrinter::visit(ElementaryTypeName const& _node)
{
writeLine(string("ElementaryTypeName ") + _node.typeName().toString());
@@ -434,11 +427,6 @@ void ASTPrinter::endVisit(EventDefinition const&)
m_indentation--;
}
-void ASTPrinter::endVisit(TypeName const&)
-{
- m_indentation--;
-}
-
void ASTPrinter::endVisit(ElementaryTypeName const&)
{
m_indentation--;
diff --git a/libsolidity/ast/ASTPrinter.h b/libsolidity/ast/ASTPrinter.h
index 4a37f17f..d6897dfd 100644
--- a/libsolidity/ast/ASTPrinter.h
+++ b/libsolidity/ast/ASTPrinter.h
@@ -60,7 +60,6 @@ public:
bool visit(ModifierDefinition const& _node) override;
bool visit(ModifierInvocation const& _node) override;
bool visit(EventDefinition const& _node) override;
- bool visit(TypeName const& _node) override;
bool visit(ElementaryTypeName const& _node) override;
bool visit(UserDefinedTypeName const& _node) override;
bool visit(FunctionTypeName const& _node) override;
@@ -104,7 +103,6 @@ public:
void endVisit(ModifierDefinition const&) override;
void endVisit(ModifierInvocation const&) override;
void endVisit(EventDefinition const&) override;
- void endVisit(TypeName const&) override;
void endVisit(ElementaryTypeName const&) override;
void endVisit(UserDefinedTypeName const&) override;
void endVisit(FunctionTypeName const&) override;
@@ -146,7 +144,7 @@ private:
std::string m_source;
ASTNode const* m_ast;
GasEstimator::ASTGasConsumption m_gasCosts;
- std::ostream* m_ostream;
+ std::ostream* m_ostream = nullptr;
};
}
diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h
index 20be634b..b726d592 100644
--- a/libsolidity/ast/ASTVisitor.h
+++ b/libsolidity/ast/ASTVisitor.h
@@ -58,7 +58,6 @@ public:
virtual bool visit(ModifierDefinition& _node) { return visitNode(_node); }
virtual bool visit(ModifierInvocation& _node) { return visitNode(_node); }
virtual bool visit(EventDefinition& _node) { return visitNode(_node); }
- virtual bool visit(TypeName& _node) { return visitNode(_node); }
virtual bool visit(ElementaryTypeName& _node) { return visitNode(_node); }
virtual bool visit(UserDefinedTypeName& _node) { return visitNode(_node); }
virtual bool visit(FunctionTypeName& _node) { return visitNode(_node); }
@@ -104,7 +103,6 @@ public:
virtual void endVisit(ModifierDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(ModifierInvocation& _node) { endVisitNode(_node); }
virtual void endVisit(EventDefinition& _node) { endVisitNode(_node); }
- virtual void endVisit(TypeName& _node) { endVisitNode(_node); }
virtual void endVisit(ElementaryTypeName& _node) { endVisitNode(_node); }
virtual void endVisit(UserDefinedTypeName& _node) { endVisitNode(_node); }
virtual void endVisit(FunctionTypeName& _node) { endVisitNode(_node); }
@@ -162,7 +160,6 @@ public:
virtual bool visit(ModifierDefinition const& _node) { return visitNode(_node); }
virtual bool visit(ModifierInvocation const& _node) { return visitNode(_node); }
virtual bool visit(EventDefinition const& _node) { return visitNode(_node); }
- virtual bool visit(TypeName const& _node) { return visitNode(_node); }
virtual bool visit(ElementaryTypeName const& _node) { return visitNode(_node); }
virtual bool visit(UserDefinedTypeName const& _node) { return visitNode(_node); }
virtual bool visit(FunctionTypeName const& _node) { return visitNode(_node); }
@@ -208,7 +205,6 @@ public:
virtual void endVisit(ModifierDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(ModifierInvocation const& _node) { endVisitNode(_node); }
virtual void endVisit(EventDefinition const& _node) { endVisitNode(_node); }
- virtual void endVisit(TypeName const& _node) { endVisitNode(_node); }
virtual void endVisit(ElementaryTypeName const& _node) { endVisitNode(_node); }
virtual void endVisit(UserDefinedTypeName const& _node) { endVisitNode(_node); }
virtual void endVisit(FunctionTypeName const& _node) { endVisitNode(_node); }
diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h
index 7c1c64b0..904d9ff6 100644
--- a/libsolidity/ast/AST_accept.h
+++ b/libsolidity/ast/AST_accept.h
@@ -291,18 +291,6 @@ void EventDefinition::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
-void TypeName::accept(ASTVisitor& _visitor)
-{
- _visitor.visit(*this);
- _visitor.endVisit(*this);
-}
-
-void TypeName::accept(ASTConstVisitor& _visitor) const
-{
- _visitor.visit(*this);
- _visitor.endVisit(*this);
-}
-
void ElementaryTypeName::accept(ASTVisitor& _visitor)
{
_visitor.visit(*this);
diff --git a/libsolidity/ast/ExperimentalFeatures.h b/libsolidity/ast/ExperimentalFeatures.h
new file mode 100644
index 00000000..2c089671
--- /dev/null
+++ b/libsolidity/ast/ExperimentalFeatures.h
@@ -0,0 +1,53 @@
+/*
+ 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/>.
+*/
+/**
+ * List of experimental features.
+ */
+
+#pragma once
+
+#include <map>
+
+namespace dev
+{
+namespace solidity
+{
+
+enum class ExperimentalFeature
+{
+ SMTChecker,
+ ABIEncoderV2, // new ABI encoder that makes use of JULIA
+ Test,
+ TestOnlyAnalysis
+};
+
+static const std::map<ExperimentalFeature, bool> ExperimentalFeatureOnlyAnalysis =
+{
+ { ExperimentalFeature::SMTChecker, true },
+ { ExperimentalFeature::TestOnlyAnalysis, true },
+};
+
+static const std::map<std::string, ExperimentalFeature> ExperimentalFeatureNames =
+{
+ { "SMTChecker", ExperimentalFeature::SMTChecker },
+ { "ABIEncoderV2", ExperimentalFeature::ABIEncoderV2 },
+ { "__test", ExperimentalFeature::Test },
+ { "__testOnlyAnalysis", ExperimentalFeature::TestOnlyAnalysis },
+};
+
+}
+}
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp
index 56fdd508..5e61cdee 100644
--- a/libsolidity/ast/Types.cpp
+++ b/libsolidity/ast/Types.cpp
@@ -477,8 +477,8 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons
if (isAddress())
return {
{"balance", make_shared<IntegerType >(256)},
- {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCall, true, false, true)},
- {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCallCode, true, false, true)},
+ {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCall, true, StateMutability::Payable)},
+ {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCallCode, true, StateMutability::Payable)},
{"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareDelegateCall, true)},
{"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)},
{"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)}
@@ -525,19 +525,20 @@ bool FixedPointType::isExplicitlyConvertibleTo(Type const& _convertTo) const
TypePointer FixedPointType::unaryOperatorResult(Token::Value _operator) const
{
- // "delete" is ok for all fixed types
- if (_operator == Token::Delete)
+ switch(_operator)
+ {
+ case Token::Delete:
+ // "delete" is ok for all fixed types
return make_shared<TupleType>();
- // for fixed, we allow +, -, ++ and --
- else if (
- _operator == Token::Add ||
- _operator == Token::Sub ||
- _operator == Token::Inc ||
- _operator == Token::Dec
- )
+ case Token::Add:
+ case Token::Sub:
+ case Token::Inc:
+ case Token::Dec:
+ // for fixed, we allow +, -, ++ and --
return shared_from_this();
- else
+ default:
return TypePointer();
+ }
}
bool FixedPointType::operator==(Type const& _other) const
@@ -738,18 +739,18 @@ bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const
{
if (_convertTo.category() == Category::Integer)
{
- auto targetType = dynamic_cast<IntegerType const*>(&_convertTo);
if (m_value == rational(0))
return true;
if (isFractional())
return false;
- int forSignBit = (targetType->isSigned() ? 1 : 0);
+ IntegerType const& targetType = dynamic_cast<IntegerType const&>(_convertTo);
+ int forSignBit = (targetType.isSigned() ? 1 : 0);
if (m_value > rational(0))
{
- if (m_value.numerator() <= (u256(-1) >> (256 - targetType->numBits() + forSignBit)))
+ if (m_value.numerator() <= (u256(-1) >> (256 - targetType.numBits() + forSignBit)))
return true;
}
- else if (targetType->isSigned() && -m_value.numerator() <= (u256(1) << (targetType->numBits() - forSignBit)))
+ else if (targetType.isSigned() && -m_value.numerator() <= (u256(1) << (targetType.numBits() - forSignBit)))
return true;
return false;
}
@@ -1408,6 +1409,11 @@ unsigned ArrayType::calldataEncodedSize(bool _padded) const
return unsigned(size);
}
+bool ArrayType::isDynamicallyEncoded() const
+{
+ return isDynamicallySized() || baseType()->isDynamicallyEncoded();
+}
+
u256 ArrayType::storageSize() const
{
if (isDynamicallySized())
@@ -1523,8 +1529,6 @@ TypePointer ArrayType::interfaceType(bool _inLibrary) const
TypePointer baseExt = m_baseType->interfaceType(_inLibrary);
if (!baseExt)
return TypePointer();
- if (m_baseType->category() == Category::Array && m_baseType->isDynamicallySized())
- return TypePointer();
if (isDynamicallySized())
return make_shared<ArrayType>(DataLocation::Memory, baseExt);
@@ -1710,6 +1714,11 @@ unsigned StructType::calldataEncodedSize(bool _padded) const
return size;
}
+bool StructType::isDynamicallyEncoded() const
+{
+ solAssert(false, "Structs are not yet supported in the ABI.");
+}
+
u256 StructType::memorySize() const
{
u256 size;
@@ -1989,8 +1998,7 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons
FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal):
m_kind(_isInternal ? Kind::Internal : Kind::External),
- m_isConstant(_function.isDeclaredConst()),
- m_isPayable(_isInternal ? false : _function.isPayable()),
+ m_stateMutability(_function.stateMutability()),
m_declaration(&_function)
{
TypePointers params;
@@ -1998,6 +2006,9 @@ FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal
TypePointers retParams;
vector<string> retParamNames;
+ if (_isInternal && m_stateMutability == StateMutability::Payable)
+ m_stateMutability = StateMutability::NonPayable;
+
params.reserve(_function.parameters().size());
paramNames.reserve(_function.parameters().size());
for (ASTPointer<VariableDeclaration> const& var: _function.parameters())
@@ -2019,7 +2030,7 @@ FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal
}
FunctionType::FunctionType(VariableDeclaration const& _varDecl):
- m_kind(Kind::External), m_isConstant(true), m_declaration(&_varDecl)
+ m_kind(Kind::External), m_stateMutability(StateMutability::View), m_declaration(&_varDecl)
{
TypePointers paramTypes;
vector<string> paramNames;
@@ -2079,7 +2090,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
}
FunctionType::FunctionType(EventDefinition const& _event):
- m_kind(Kind::Event), m_isConstant(true), m_declaration(&_event)
+ m_kind(Kind::Event), m_stateMutability(StateMutability::View), m_declaration(&_event)
{
TypePointers params;
vector<string> paramNames;
@@ -2096,14 +2107,10 @@ FunctionType::FunctionType(EventDefinition const& _event):
FunctionType::FunctionType(FunctionTypeName const& _typeName):
m_kind(_typeName.visibility() == VariableDeclaration::Visibility::External ? Kind::External : Kind::Internal),
- m_isConstant(_typeName.isDeclaredConst()),
- m_isPayable(_typeName.isPayable())
+ m_stateMutability(_typeName.stateMutability())
{
if (_typeName.isPayable())
- {
solAssert(m_kind == Kind::External, "Internal payable function type used.");
- solAssert(!m_isConstant, "Payable constant function");
- }
for (auto const& t: _typeName.parameterTypes())
{
solAssert(t->annotation().type, "Type not set for parameter.");
@@ -2131,7 +2138,9 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c
FunctionDefinition const* constructor = _contract.constructor();
TypePointers parameters;
strings parameterNames;
- bool payable = false;
+ StateMutability stateMutability = StateMutability::NonPayable;
+
+ solAssert(_contract.contractKind() != ContractDefinition::ContractKind::Interface, "");
if (constructor)
{
@@ -2140,8 +2149,10 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c
parameterNames.push_back(var->name());
parameters.push_back(var->annotation().type);
}
- payable = constructor->isPayable();
+ if (constructor->isPayable())
+ stateMutability = StateMutability::Payable;
}
+
return make_shared<FunctionType>(
parameters,
TypePointers{make_shared<ContractType>(_contract)},
@@ -2150,8 +2161,7 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c
Kind::Creation,
false,
nullptr,
- false,
- payable
+ stateMutability
);
}
@@ -2208,8 +2218,7 @@ string FunctionType::identifier() const
case Kind::Require: id += "require";break;
default: solAssert(false, "Unknown function location."); break;
}
- if (isConstant())
- id += "_constant";
+ id += "_" + stateMutabilityToString(m_stateMutability);
id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes);
if (m_gasSet)
id += "gas";
@@ -2224,23 +2233,21 @@ bool FunctionType::operator==(Type const& _other) const
{
if (_other.category() != category())
return false;
- FunctionType const& other = dynamic_cast<FunctionType const&>(_other);
- if (m_kind != other.m_kind)
- return false;
- if (m_isConstant != other.isConstant())
+ FunctionType const& other = dynamic_cast<FunctionType const&>(_other);
+ if (
+ m_kind != other.m_kind ||
+ m_stateMutability != other.stateMutability() ||
+ m_parameterTypes.size() != other.m_parameterTypes.size() ||
+ m_returnParameterTypes.size() != other.m_returnParameterTypes.size()
+ )
return false;
- if (m_parameterTypes.size() != other.m_parameterTypes.size() ||
- m_returnParameterTypes.size() != other.m_returnParameterTypes.size())
- return false;
auto typeCompare = [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; };
-
- if (!equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(),
- other.m_parameterTypes.cbegin(), typeCompare))
- return false;
- if (!equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(),
- other.m_returnParameterTypes.cbegin(), typeCompare))
+ if (
+ !equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(), other.m_parameterTypes.cbegin(), typeCompare) ||
+ !equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(), other.m_returnParameterTypes.cbegin(), typeCompare)
+ )
return false;
//@todo this is ugly, but cannot be prevented right now
if (m_gasSet != other.m_gasSet || m_valueSet != other.m_valueSet)
@@ -2292,10 +2299,8 @@ string FunctionType::toString(bool _short) const
for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it)
name += (*it)->toString(_short) + (it + 1 == m_parameterTypes.end() ? "" : ",");
name += ")";
- if (m_isConstant)
- name += " constant";
- if (m_isPayable)
- name += " payable";
+ if (m_stateMutability != StateMutability::NonPayable)
+ name += " " + stateMutabilityToString(m_stateMutability);
if (m_kind == Kind::External)
name += " external";
if (!m_returnParameterTypes.empty())
@@ -2344,14 +2349,26 @@ unsigned FunctionType::sizeOnStack() const
}
unsigned size = 0;
- if (kind == Kind::External || kind == Kind::CallCode || kind == Kind::DelegateCall)
+
+ switch(kind)
+ {
+ case Kind::External:
+ case Kind::CallCode:
+ case Kind::DelegateCall:
size = 2;
- else if (kind == Kind::BareCall || kind == Kind::BareCallCode || kind == Kind::BareDelegateCall)
- size = 1;
- else if (kind == Kind::Internal)
- size = 1;
- else if (kind == Kind::ArrayPush || kind == Kind::ByteArrayPush)
+ break;
+ case Kind::BareCall:
+ case Kind::BareCallCode:
+ case Kind::BareDelegateCall:
+ case Kind::Internal:
+ case Kind::ArrayPush:
+ case Kind::ByteArrayPush:
size = 1;
+ break;
+ default:
+ break;
+ }
+
if (m_gasSet)
size++;
if (m_valueSet)
@@ -2389,10 +2406,14 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const
return FunctionTypePointer();
return make_shared<FunctionType>(
- paramTypes, retParamTypes,
- m_parameterNames, m_returnParameterNames,
- m_kind, m_arbitraryParameters,
- m_declaration, m_isConstant, m_isPayable
+ paramTypes,
+ retParamTypes,
+ m_parameterNames,
+ m_returnParameterNames,
+ m_kind,
+ m_arbitraryParameters,
+ m_declaration,
+ m_stateMutability
);
}
@@ -2409,7 +2430,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
MemberList::MemberMap members;
if (m_kind != Kind::BareDelegateCall && m_kind != Kind::DelegateCall)
{
- if (m_isPayable)
+ if (isPayable())
members.push_back(MemberList::Member(
"value",
make_shared<FunctionType>(
@@ -2420,8 +2441,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
Kind::SetValue,
false,
nullptr,
- false,
- false,
+ StateMutability::NonPayable,
m_gasSet,
m_valueSet
)
@@ -2438,8 +2458,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
Kind::SetGas,
false,
nullptr,
- false,
- false,
+ StateMutability::NonPayable,
m_gasSet,
m_valueSet
)
@@ -2575,8 +2594,7 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con
m_kind,
m_arbitraryParameters,
m_declaration,
- m_isConstant,
- m_isPayable,
+ m_stateMutability,
m_gasSet || _setGas,
m_valueSet || _setValue,
m_bound
@@ -2625,8 +2643,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
kind,
m_arbitraryParameters,
m_declaration,
- m_isConstant,
- m_isPayable,
+ m_stateMutability,
m_gasSet,
m_valueSet,
_bound
diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h
index 5d2bdca0..ce2d3bf8 100644
--- a/libsolidity/ast/Types.h
+++ b/libsolidity/ast/Types.h
@@ -24,6 +24,7 @@
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/ast/ASTForward.h>
+#include <libsolidity/ast/ASTEnums.h>
#include <libsolidity/parsing/Token.h>
#include <libdevcore/Common.h>
@@ -187,6 +188,7 @@ public:
/// @returns number of bytes used by this type when encoded for CALL. If it is a dynamic type,
/// returns the size of the pointer (usually 32). Returns 0 if the type cannot be encoded
/// in calldata.
+ /// @note: This should actually not be called on types, where isDynamicallyEncoded returns true.
/// If @a _padded then it is assumed that each element is padded to a multiple of 32 bytes.
virtual unsigned calldataEncodedSize(bool _padded) const { (void)_padded; return 0; }
/// @returns the size of this data type in bytes when stored in memory. For memory-reference
@@ -194,8 +196,10 @@ public:
virtual unsigned memoryHeadSize() const { return calldataEncodedSize(); }
/// Convenience version of @see calldataEncodedSize(bool)
unsigned calldataEncodedSize() const { return calldataEncodedSize(true); }
- /// @returns true if the type is dynamically encoded in calldata
+ /// @returns true if the type is a dynamic array
virtual bool isDynamicallySized() const { return false; }
+ /// @returns true if the type is dynamically encoded in the ABI
+ virtual bool isDynamicallyEncoded() const { return false; }
/// @returns the number of storage slots required to hold this value in storage.
/// For dynamically "allocated" types, it returns the size of the statically allocated head,
virtual u256 storageSize() const { return 1; }
@@ -609,6 +613,7 @@ public:
virtual bool operator==(const Type& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded) const override;
virtual bool isDynamicallySized() const override { return m_hasDynamicLength; }
+ virtual bool isDynamicallyEncoded() const override;
virtual u256 storageSize() const override;
virtual bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); }
virtual unsigned sizeOnStack() const override;
@@ -723,6 +728,7 @@ public:
virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded) const override;
+ virtual bool isDynamicallyEncoded() const override;
u256 memorySize() const;
virtual u256 storageSize() const override;
virtual bool canLiveOutsideStorage() const override { return true; }
@@ -884,8 +890,7 @@ public:
strings const& _returnParameterTypes,
Kind _kind = Kind::Internal,
bool _arbitraryParameters = false,
- bool _constant = false,
- bool _payable = false
+ StateMutability _stateMutability = StateMutability::NonPayable
): FunctionType(
parseElementaryTypeVector(_parameterTypes),
parseElementaryTypeVector(_returnParameterTypes),
@@ -894,8 +899,7 @@ public:
_kind,
_arbitraryParameters,
nullptr,
- _constant,
- _payable
+ _stateMutability
)
{
}
@@ -912,8 +916,7 @@ public:
Kind _kind = Kind::Internal,
bool _arbitraryParameters = false,
Declaration const* _declaration = nullptr,
- bool _isConstant = false,
- bool _isPayable = false,
+ StateMutability _stateMutability = StateMutability::NonPayable,
bool _gasSet = false,
bool _valueSet = false,
bool _bound = false
@@ -923,12 +926,11 @@ public:
m_parameterNames(_parameterNames),
m_returnParameterNames(_returnParameterNames),
m_kind(_kind),
+ m_stateMutability(_stateMutability),
m_arbitraryParameters(_arbitraryParameters),
m_gasSet(_gasSet),
m_valueSet(_valueSet),
m_bound(_bound),
- m_isConstant(_isConstant),
- m_isPayable(_isPayable),
m_declaration(_declaration)
{
solAssert(
@@ -980,6 +982,7 @@ public:
/// @returns true if the ABI is used for this call (only meaningful for external calls)
bool isBareCall() const;
Kind const& kind() const { return m_kind; }
+ StateMutability stateMutability() const { return m_stateMutability; }
/// @returns the external signature of this function type given the function name
std::string externalSignature() const;
/// @returns the external identifier of this function (the hash of the signature).
@@ -990,12 +993,11 @@ public:
return *m_declaration;
}
bool hasDeclaration() const { return !!m_declaration; }
- bool isConstant() const { return m_isConstant; }
/// @returns true if the the result of this function only depends on its arguments
/// and it does not modify the state.
/// Currently, this will only return true for internal functions like keccak and ecrecover.
bool isPure() const;
- bool isPayable() const { return m_isPayable; }
+ bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
/// @return A shared pointer of an ASTString.
/// Can contain a nullptr in which case indicates absence of documentation
ASTPointer<ASTString> documentation() const;
@@ -1028,13 +1030,12 @@ private:
std::vector<std::string> m_parameterNames;
std::vector<std::string> m_returnParameterNames;
Kind const m_kind;
+ StateMutability m_stateMutability = StateMutability::NonPayable;
/// true if the function takes an arbitrary number of arguments of arbitrary types
bool const m_arbitraryParameters = false;
bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack
bool const m_valueSet = false; ///< true iff the value to be sent is on the stack
bool const m_bound = false; ///< true iff the function is called as arg1.fun(arg2, ..., argn)
- bool m_isConstant = false;
- bool m_isPayable = false;
Declaration const* m_declaration = nullptr;
};
diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp
new file mode 100644
index 00000000..a2938ed7
--- /dev/null
+++ b/libsolidity/codegen/ABIFunctions.cpp
@@ -0,0 +1,1074 @@
+/*
+ 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/>.
+*/
+/**
+ * @author Christian <chris@ethereum.org>
+ * @date 2017
+ * Routines that generate JULIA code related to ABI encoding, decoding and type conversions.
+ */
+
+#include <libsolidity/codegen/ABIFunctions.h>
+
+#include <libdevcore/Whiskers.h>
+
+#include <libsolidity/ast/AST.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+ABIFunctions::~ABIFunctions()
+{
+ // This throws an exception and thus might cause immediate termination, but hey,
+ // it's a failed assertion anyway :-)
+ solAssert(m_requestedFunctions.empty(), "Forgot to call ``requestedFunctions()``.");
+}
+
+string ABIFunctions::tupleEncoder(
+ TypePointers const& _givenTypes,
+ TypePointers const& _targetTypes,
+ bool _encodeAsLibraryTypes
+)
+{
+ // stack: <$value0> <$value1> ... <$value(n-1)> <$headStart>
+
+ solAssert(!_givenTypes.empty(), "");
+ size_t const headSize_ = headSize(_targetTypes);
+
+ Whiskers encoder(R"(
+ {
+ let tail := add($headStart, <headSize>)
+ <encodeElements>
+ <deepestStackElement> := tail
+ }
+ )");
+ encoder("headSize", to_string(headSize_));
+ string encodeElements;
+ size_t headPos = 0;
+ size_t stackPos = 0;
+ for (size_t i = 0; i < _givenTypes.size(); ++i)
+ {
+ solAssert(_givenTypes[i], "");
+ solAssert(_targetTypes[i], "");
+ size_t sizeOnStack = _givenTypes[i]->sizeOnStack();
+ string valueNames = "";
+ for (size_t j = 0; j < sizeOnStack; j++)
+ valueNames += "$value" + to_string(stackPos++) + ", ";
+ bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
+ Whiskers elementTempl(
+ dynamic ?
+ string(R"(
+ mstore(add($headStart, <pos>), sub(tail, $headStart))
+ tail := <abiEncode>(<values> tail)
+ )") :
+ string(R"(
+ <abiEncode>(<values> add($headStart, <pos>))
+ )")
+ );
+ elementTempl("values", valueNames);
+ elementTempl("pos", to_string(headPos));
+ elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], _encodeAsLibraryTypes, false));
+ encodeElements += elementTempl.render();
+ headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize();
+ }
+ solAssert(headPos == headSize_, "");
+ encoder("encodeElements", encodeElements);
+ encoder("deepestStackElement", stackPos > 0 ? "$value0" : "$headStart");
+
+ return encoder.render();
+}
+
+string ABIFunctions::requestedFunctions()
+{
+ string result;
+ for (auto const& f: m_requestedFunctions)
+ result += f.second;
+ m_requestedFunctions.clear();
+ return result;
+}
+
+string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
+{
+ string functionName = string("cleanup_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
+ return createFunction(functionName, [&]() {
+ Whiskers templ(R"(
+ function <functionName>(value) -> cleaned {
+ <body>
+ }
+ )");
+ templ("functionName", functionName);
+ switch (_type.category())
+ {
+ case Type::Category::Integer:
+ {
+ IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
+ if (type.numBits() == 256)
+ templ("body", "cleaned := value");
+ else if (type.isSigned())
+ templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)");
+ else
+ templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")");
+ break;
+ }
+ case Type::Category::RationalNumber:
+ templ("body", "cleaned := value");
+ break;
+ case Type::Category::Bool:
+ templ("body", "cleaned := iszero(iszero(value))");
+ break;
+ case Type::Category::FixedPoint:
+ 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.");
+ break;
+ case Type::Category::FixedBytes:
+ {
+ FixedBytesType const& type = dynamic_cast<FixedBytesType const&>(_type);
+ if (type.numBytes() == 32)
+ templ("body", "cleaned := value");
+ else if (type.numBytes() == 0)
+ templ("body", "cleaned := 0");
+ else
+ {
+ size_t numBits = type.numBytes() * 8;
+ u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits);
+ templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")");
+ }
+ break;
+ }
+ case Type::Category::Contract:
+ templ("body", "cleaned := " + cleanupFunction(IntegerType(0, IntegerType::Modifier::Address)) + "(value)");
+ break;
+ case Type::Category::Enum:
+ {
+ 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");
+ w("members", to_string(members));
+ if (_revertOnFailure)
+ w("failure", "revert(0, 0)");
+ else
+ w("failure", "invalid()");
+ templ("body", w.render());
+ break;
+ }
+ default:
+ solAssert(false, "Cleanup of type " + _type.identifier() + " requested.");
+ }
+
+ return templ.render();
+ });
+}
+
+string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
+{
+ string functionName =
+ "convert_" +
+ _from.identifier() +
+ "_to_" +
+ _to.identifier();
+ return createFunction(functionName, [&]() {
+ Whiskers templ(R"(
+ function <functionName>(value) -> converted {
+ <body>
+ }
+ )");
+ templ("functionName", functionName);
+ string body;
+ auto toCategory = _to.category();
+ auto fromCategory = _from.category();
+ switch (fromCategory)
+ {
+ case Type::Category::Integer:
+ case Type::Category::RationalNumber:
+ case Type::Category::Contract:
+ {
+ if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&_from))
+ solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType.");
+ if (toCategory == Type::Category::FixedBytes)
+ {
+ solAssert(
+ fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber,
+ "Invalid conversion to FixedBytesType requested."
+ );
+ FixedBytesType const& toBytesType = dynamic_cast<FixedBytesType const&>(_to);
+ body =
+ Whiskers("converted := <shiftLeft>(<clean>(value))")
+ ("shiftLeft", shiftLeftFunction(256 - toBytesType.numBytes() * 8))
+ ("clean", cleanupFunction(_from))
+ .render();
+ }
+ else if (toCategory == Type::Category::Enum)
+ {
+ solAssert(_from.mobileType(), "");
+ body =
+ Whiskers("converted := <cleanEnum>(<cleanInt>(value))")
+ ("cleanEnum", cleanupFunction(_to, false))
+ // "mobileType()" returns integer type for rational
+ ("cleanInt", cleanupFunction(*_from.mobileType()))
+ .render();
+ }
+ else if (toCategory == Type::Category::FixedPoint)
+ {
+ solUnimplemented("Not yet implemented - FixedPointType.");
+ }
+ else
+ {
+ solAssert(
+ toCategory == Type::Category::Integer ||
+ toCategory == Type::Category::Contract,
+ "");
+ IntegerType const addressType(0, IntegerType::Modifier::Address);
+ IntegerType const& to =
+ toCategory == Type::Category::Integer ?
+ dynamic_cast<IntegerType const&>(_to) :
+ addressType;
+
+ // Clean according to the "to" type, except if this is
+ // a widening conversion.
+ IntegerType const* cleanupType = &to;
+ if (fromCategory != Type::Category::RationalNumber)
+ {
+ IntegerType const& from =
+ fromCategory == Type::Category::Integer ?
+ dynamic_cast<IntegerType const&>(_from) :
+ addressType;
+ if (to.numBits() > from.numBits())
+ cleanupType = &from;
+ }
+ body =
+ Whiskers("converted := <cleanInt>(value)")
+ ("cleanInt", cleanupFunction(*cleanupType))
+ .render();
+ }
+ break;
+ }
+ case Type::Category::Bool:
+ {
+ solAssert(_from == _to, "Invalid conversion for bool.");
+ body =
+ Whiskers("converted := <clean>(value)")
+ ("clean", cleanupFunction(_from))
+ .render();
+ break;
+ }
+ case Type::Category::FixedPoint:
+ solUnimplemented("Fixed point types not implemented.");
+ break;
+ case Type::Category::Array:
+ solUnimplementedAssert(false, "Array conversion not implemented.");
+ break;
+ case Type::Category::Struct:
+ solUnimplementedAssert(false, "Struct conversion not implemented.");
+ break;
+ case Type::Category::FixedBytes:
+ {
+ FixedBytesType const& from = dynamic_cast<FixedBytesType const&>(_from);
+ if (toCategory == Type::Category::Integer)
+ body =
+ Whiskers("converted := <convert>(<shift>(value))")
+ ("shift", shiftRightFunction(256 - from.numBytes() * 8, false))
+ ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to))
+ .render();
+ else
+ {
+ // clear for conversion to longer bytes
+ solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
+ body =
+ Whiskers("converted := <clean>(value)")
+ ("clean", cleanupFunction(from))
+ .render();
+ }
+ break;
+ }
+ case Type::Category::Function:
+ {
+ solAssert(false, "Conversion should not be called for function types.");
+ break;
+ }
+ case Type::Category::Enum:
+ {
+ solAssert(toCategory == Type::Category::Integer || _from == _to, "");
+ EnumType const& enumType = dynamic_cast<decltype(enumType)>(_from);
+ body =
+ Whiskers("converted := <clean>(value)")
+ ("clean", cleanupFunction(enumType))
+ .render();
+ break;
+ }
+ case Type::Category::Tuple:
+ {
+ solUnimplementedAssert(false, "Tuple conversion not implemented.");
+ break;
+ }
+ default:
+ solAssert(false, "");
+ }
+
+ solAssert(!body.empty(), "");
+ templ("body", body);
+ return templ.render();
+ });
+}
+
+string ABIFunctions::cleanupCombinedExternalFunctionIdFunction()
+{
+ string functionName = "cleanup_combined_external_function_id";
+ return createFunction(functionName, [&]() {
+ return Whiskers(R"(
+ function <functionName>(addr_and_selector) -> cleaned {
+ cleaned := <clean>(addr_and_selector)
+ }
+ )")
+ ("functionName", functionName)
+ ("clean", cleanupFunction(FixedBytesType(24)))
+ .render();
+ });
+}
+
+string ABIFunctions::combineExternalFunctionIdFunction()
+{
+ string functionName = "combine_external_function_id";
+ return createFunction(functionName, [&]() {
+ return Whiskers(R"(
+ function <functionName>(addr, selector) -> combined {
+ combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff)))
+ }
+ )")
+ ("functionName", functionName)
+ ("shl32", shiftLeftFunction(32))
+ ("shl64", shiftLeftFunction(64))
+ .render();
+ });
+}
+
+string ABIFunctions::abiEncodingFunction(
+ Type const& _from,
+ Type const& _to,
+ bool _encodeAsLibraryTypes,
+ bool _compacted
+)
+{
+ solUnimplementedAssert(
+ _to.mobileType() &&
+ _to.mobileType()->interfaceType(_encodeAsLibraryTypes) &&
+ _to.mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(),
+ "Encoding type \"" + _to.toString() + "\" not yet implemented."
+ );
+ TypePointer toInterface = _to.mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType();
+ Type const& to = *toInterface;
+
+ if (_from.category() == Type::Category::StringLiteral)
+ return abiEncodingFunctionStringLiteral(_from, to, _encodeAsLibraryTypes);
+ else if (auto toArray = dynamic_cast<ArrayType const*>(&to))
+ {
+ solAssert(_from.category() == Type::Category::Array, "");
+ solAssert(to.dataStoredIn(DataLocation::Memory), "");
+ ArrayType const& fromArray = dynamic_cast<ArrayType const&>(_from);
+ if (fromArray.location() == DataLocation::CallData)
+ return abiEncodingFunctionCalldataArray(fromArray, *toArray, _encodeAsLibraryTypes);
+ else if (!fromArray.isByteArray() && (
+ fromArray.location() == DataLocation::Memory ||
+ fromArray.baseType()->storageBytes() > 16
+ ))
+ return abiEncodingFunctionSimpleArray(fromArray, *toArray, _encodeAsLibraryTypes);
+ else if (fromArray.location() == DataLocation::Memory)
+ return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _encodeAsLibraryTypes);
+ else if (fromArray.location() == DataLocation::Storage)
+ return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _encodeAsLibraryTypes);
+ else
+ solAssert(false, "");
+ }
+ else if (dynamic_cast<StructType const*>(&to))
+ {
+ solUnimplementedAssert(false, "Structs not yet implemented.");
+ }
+ else if (_from.category() == Type::Category::Function)
+ return abiEncodingFunctionFunctionType(
+ dynamic_cast<FunctionType const&>(_from),
+ to,
+ _encodeAsLibraryTypes,
+ _compacted
+ );
+
+ solAssert(_from.sizeOnStack() == 1, "");
+ solAssert(to.isValueType(), "");
+ solAssert(to.calldataEncodedSize() == 32, "");
+ string functionName =
+ "abi_encode_" +
+ _from.identifier() +
+ "_to_" +
+ to.identifier() +
+ (_encodeAsLibraryTypes ? "_library" : "");
+ return createFunction(functionName, [&]() {
+ solAssert(!to.isDynamicallyEncoded(), "");
+
+ Whiskers templ(R"(
+ function <functionName>(value, pos) {
+ mstore(pos, <cleanupConvert>)
+ }
+ )");
+ templ("functionName", functionName);
+
+ if (_from.dataStoredIn(DataLocation::Storage) && to.isValueType())
+ {
+ // special case: convert storage reference type to value type - this is only
+ // possible for library calls where we just forward the storage reference
+ solAssert(_encodeAsLibraryTypes, "");
+ solAssert(to == IntegerType(256), "");
+ templ("cleanupConvert", "value");
+ }
+ else
+ {
+ if (_from == to)
+ templ("cleanupConvert", cleanupFunction(_from) + "(value)");
+ else
+ templ("cleanupConvert", conversionFunction(_from, to) + "(value)");
+ }
+ return templ.render();
+ });
+}
+
+string ABIFunctions::abiEncodingFunctionCalldataArray(
+ Type const& _from,
+ Type const& _to,
+ bool _encodeAsLibraryTypes
+)
+{
+ solAssert(_to.isDynamicallySized(), "");
+ solAssert(_from.category() == Type::Category::Array, "Unknown dynamic type.");
+ solAssert(_to.category() == Type::Category::Array, "Unknown dynamic type.");
+ auto const& fromArrayType = dynamic_cast<ArrayType const&>(_from);
+ auto const& toArrayType = dynamic_cast<ArrayType const&>(_to);
+
+ solAssert(fromArrayType.location() == DataLocation::CallData, "");
+
+ solAssert(
+ *fromArrayType.copyForLocation(DataLocation::Memory, true) ==
+ *toArrayType.copyForLocation(DataLocation::Memory, true),
+ ""
+ );
+
+ string functionName =
+ "abi_encode_" +
+ _from.identifier() +
+ "_to_" +
+ _to.identifier() +
+ (_encodeAsLibraryTypes ? "_library" : "");
+ return createFunction(functionName, [&]() {
+ solUnimplementedAssert(fromArrayType.isByteArray(), "");
+ // 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"(
+ function <functionName>(start, length, pos) -> end {
+ <storeLength> // might update pos
+ <copyFun>(start, pos, length)
+ end := add(pos, <roundUpFun>(length))
+ }
+ )");
+ templ("storeLength", _to.isDynamicallySized() ? "mstore(pos, length) pos := add(pos, 0x20)" : "");
+ templ("functionName", functionName);
+ templ("copyFun", copyToMemoryFunction(true));
+ templ("roundUpFun", roundUpFunction());
+ return templ.render();
+ });
+}
+
+string ABIFunctions::abiEncodingFunctionSimpleArray(
+ ArrayType const& _from,
+ ArrayType const& _to,
+ bool _encodeAsLibraryTypes
+)
+{
+ string functionName =
+ "abi_encode_" +
+ _from.identifier() +
+ "_to_" +
+ _to.identifier() +
+ (_encodeAsLibraryTypes ? "_library" : "");
+
+ solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
+ solAssert(_from.length() == _to.length(), "");
+ solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.dataStoredIn(DataLocation::Storage), "");
+ solAssert(!_from.isByteArray(), "");
+ solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.baseType()->storageBytes() > 16, "");
+
+ return createFunction(functionName, [&]() {
+ bool dynamic = _to.isDynamicallyEncoded();
+ bool dynamicBase = _to.baseType()->isDynamicallyEncoded();
+ bool inMemory = _from.dataStoredIn(DataLocation::Memory);
+ Whiskers templ(
+ dynamicBase ?
+ R"(
+ function <functionName>(value, pos) <return> {
+ let length := <lengthFun>(value)
+ <storeLength> // might update pos
+ let headStart := pos
+ let tail := add(pos, mul(length, 0x20))
+ let srcPtr := <dataAreaFun>(value)
+ for { let i := 0 } lt(i, length) { i := add(i, 1) }
+ {
+ mstore(pos, sub(tail, headStart))
+ tail := <encodeToMemoryFun>(<arrayElementAccess>(srcPtr), tail)
+ srcPtr := <nextArrayElement>(srcPtr)
+ pos := add(pos, <elementEncodedSize>)
+ }
+ pos := tail
+ <assignEnd>
+ }
+ )" :
+ R"(
+ function <functionName>(value, pos) <return> {
+ let length := <lengthFun>(value)
+ <storeLength> // might update pos
+ let srcPtr := <dataAreaFun>(value)
+ for { let i := 0 } lt(i, length) { i := add(i, 1) }
+ {
+ <encodeToMemoryFun>(<arrayElementAccess>(srcPtr), pos)
+ srcPtr := <nextArrayElement>(srcPtr)
+ pos := add(pos, <elementEncodedSize>)
+ }
+ <assignEnd>
+ }
+ )"
+ );
+ templ("functionName", functionName);
+ templ("return", dynamic ? " -> end " : "");
+ templ("assignEnd", dynamic ? "end := pos" : "");
+ templ("lengthFun", arrayLengthFunction(_from));
+ if (_to.isDynamicallySized())
+ templ("storeLength", "mstore(pos, length) pos := add(pos, 0x20)");
+ else
+ templ("storeLength", "");
+ templ("dataAreaFun", arrayDataAreaFunction(_from));
+ templ("elementEncodedSize", toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize()));
+ templ("encodeToMemoryFun", abiEncodingFunction(
+ *_from.baseType(),
+ *_to.baseType(),
+ _encodeAsLibraryTypes,
+ true
+ ));
+ templ("arrayElementAccess", inMemory ? "mload" : "sload");
+ templ("nextArrayElement", nextArrayElementFunction(_from));
+ return templ.render();
+ });
+}
+
+string ABIFunctions::abiEncodingFunctionMemoryByteArray(
+ ArrayType const& _from,
+ ArrayType const& _to,
+ bool _encodeAsLibraryTypes
+)
+{
+ string functionName =
+ "abi_encode_" +
+ _from.identifier() +
+ "_to_" +
+ _to.identifier() +
+ (_encodeAsLibraryTypes ? "_library" : "");
+
+ solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
+ solAssert(_from.length() == _to.length(), "");
+ solAssert(_from.dataStoredIn(DataLocation::Memory), "");
+ solAssert(_from.isByteArray(), "");
+
+ return createFunction(functionName, [&]() {
+ solAssert(_to.isByteArray(), "");
+ Whiskers templ(R"(
+ function <functionName>(value, pos) -> end {
+ let length := <lengthFun>(value)
+ mstore(pos, length)
+ <copyFun>(add(value, 0x20), add(pos, 0x20), length)
+ end := add(add(pos, 0x20), <roundUpFun>(length))
+ }
+ )");
+ templ("functionName", functionName);
+ templ("lengthFun", arrayLengthFunction(_from));
+ templ("copyFun", copyToMemoryFunction(false));
+ templ("roundUpFun", roundUpFunction());
+ return templ.render();
+ });
+}
+
+string ABIFunctions::abiEncodingFunctionCompactStorageArray(
+ ArrayType const& _from,
+ ArrayType const& _to,
+ bool _encodeAsLibraryTypes
+)
+{
+ string functionName =
+ "abi_encode_" +
+ _from.identifier() +
+ "_to_" +
+ _to.identifier() +
+ (_encodeAsLibraryTypes ? "_library" : "");
+
+ solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), "");
+ solAssert(_from.length() == _to.length(), "");
+ solAssert(_from.dataStoredIn(DataLocation::Storage), "");
+
+ return createFunction(functionName, [&]() {
+ if (_from.isByteArray())
+ {
+ solAssert(_to.isByteArray(), "");
+ Whiskers templ(R"(
+ function <functionName>(value, pos) -> ret {
+ let slotValue := sload(value)
+ switch and(slotValue, 1)
+ case 0 {
+ // short byte array
+ let length := and(div(slotValue, 2), 0x7f)
+ mstore(pos, length)
+ mstore(add(pos, 0x20), and(slotValue, not(0xff)))
+ ret := add(pos, 0x40)
+ }
+ case 1 {
+ // long byte array
+ let length := div(slotValue, 2)
+ mstore(pos, length)
+ pos := add(pos, 0x20)
+ let dataPos := <arrayDataSlot>(value)
+ let i := 0
+ for { } lt(i, length) { i := add(i, 0x20) } {
+ mstore(add(pos, i), sload(dataPos))
+ dataPos := add(dataPos, 1)
+ }
+ ret := add(pos, i)
+ }
+ }
+ )");
+ templ("functionName", functionName);
+ templ("arrayDataSlot", arrayDataAreaFunction(_from));
+ return templ.render();
+ }
+ else
+ {
+ // Multiple items per slot
+ solAssert(_from.baseType()->storageBytes() <= 16, "");
+ solAssert(!_from.baseType()->isDynamicallyEncoded(), "");
+ solAssert(_from.baseType()->isValueType(), "");
+ bool dynamic = _to.isDynamicallyEncoded();
+ size_t storageBytes = _from.baseType()->storageBytes();
+ size_t itemsPerSlot = 32 / storageBytes;
+ // This always writes full slot contents to memory, which might be
+ // more than desired, i.e. it writes beyond the end of memory.
+ Whiskers templ(
+ R"(
+ function <functionName>(value, pos) <return> {
+ let length := <lengthFun>(value)
+ <storeLength> // might update pos
+ let originalPos := pos
+ let srcPtr := <dataArea>(value)
+ for { let i := 0 } lt(i, length) { i := add(i, <itemsPerSlot>) }
+ {
+ let data := sload(srcPtr)
+ <#items>
+ <encodeToMemoryFun>(<shiftRightFun>(data), pos)
+ pos := add(pos, <elementEncodedSize>)
+ </items>
+ srcPtr := add(srcPtr, 1)
+ }
+ pos := add(originalPos, mul(length, <elementEncodedSize>))
+ <assignEnd>
+ }
+ )"
+ );
+ templ("functionName", functionName);
+ templ("return", dynamic ? " -> end " : "");
+ templ("assignEnd", dynamic ? "end := pos" : "");
+ templ("lengthFun", arrayLengthFunction(_from));
+ if (_to.isDynamicallySized())
+ templ("storeLength", "mstore(pos, length) pos := add(pos, 0x20)");
+ else
+ templ("storeLength", "");
+ templ("dataArea", arrayDataAreaFunction(_from));
+ templ("itemsPerSlot", to_string(itemsPerSlot));
+ string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize());
+ templ("elementEncodedSize", elementEncodedSize);
+ string encodeToMemoryFun = abiEncodingFunction(
+ *_from.baseType(),
+ *_to.baseType(),
+ _encodeAsLibraryTypes,
+ true
+ );
+ templ("encodeToMemoryFun", encodeToMemoryFun);
+ std::vector<std::map<std::string, std::string>> items(itemsPerSlot);
+ for (size_t i = 0; i < itemsPerSlot; ++i)
+ items[i]["shiftRightFun"] = shiftRightFunction(i * storageBytes * 8, false);
+ templ("items", items);
+ return templ.render();
+ }
+ });
+}
+
+string ABIFunctions::abiEncodingFunctionStringLiteral(
+ Type const& _from,
+ Type const& _to,
+ bool _encodeAsLibraryTypes
+)
+{
+ solAssert(_from.category() == Type::Category::StringLiteral, "");
+
+ string functionName =
+ "abi_encode_" +
+ _from.identifier() +
+ "_to_" +
+ _to.identifier() +
+ (_encodeAsLibraryTypes ? "_library" : "");
+ return createFunction(functionName, [&]() {
+ auto const& strType = dynamic_cast<StringLiteralType const&>(_from);
+ string const& value = strType.value();
+ solAssert(_from.sizeOnStack() == 0, "");
+
+ if (_to.isDynamicallySized())
+ {
+ Whiskers templ(R"(
+ function <functionName>(pos) -> end {
+ mstore(pos, <length>)
+ <#word>
+ mstore(add(pos, <offset>), <wordValue>)
+ </word>
+ end := add(pos, <overallSize>)
+ }
+ )");
+ templ("functionName", functionName);
+
+ // TODO this can make use of CODECOPY for large strings once we have that in JULIA
+ size_t words = (value.size() + 31) / 32;
+ templ("overallSize", to_string(32 + words * 32));
+ templ("length", to_string(value.size()));
+ vector<map<string, string>> wordParams(words);
+ for (size_t i = 0; i < words; ++i)
+ {
+ wordParams[i]["offset"] = to_string(32 + i * 32);
+ wordParams[i]["wordValue"] = "0x" + h256(value.substr(32 * i, 32), h256::AlignLeft).hex();
+ }
+ templ("word", wordParams);
+ return templ.render();
+ }
+ else
+ {
+ solAssert(_to.category() == Type::Category::FixedBytes, "");
+ solAssert(value.size() <= 32, "");
+ Whiskers templ(R"(
+ function <functionName>(pos) {
+ mstore(pos, <wordValue>)
+ }
+ )");
+ templ("functionName", functionName);
+ templ("wordValue", "0x" + h256(value, h256::AlignLeft).hex());
+ return templ.render();
+ }
+ });
+}
+
+string ABIFunctions::abiEncodingFunctionFunctionType(
+ FunctionType const& _from,
+ Type const& _to,
+ bool _encodeAsLibraryTypes,
+ bool _compacted
+)
+{
+ solAssert(_from.kind() == FunctionType::Kind::External, "");
+ solAssert(_from == _to, "");
+
+ string functionName =
+ "abi_encode_" +
+ _from.identifier() +
+ "_to_" +
+ _to.identifier() +
+ (_compacted ? "_compacted" : "") +
+ (_encodeAsLibraryTypes ? "_library" : "");
+
+ if (_compacted)
+ {
+ return createFunction(functionName, [&]() {
+ return Whiskers(R"(
+ function <functionName>(addr_and_function_id, pos) {
+ mstore(pos, <cleanExtFun>(addr_and_function_id))
+ }
+ )")
+ ("functionName", functionName)
+ ("cleanExtFun", cleanupCombinedExternalFunctionIdFunction())
+ .render();
+ });
+ }
+ else
+ {
+ return createFunction(functionName, [&]() {
+ return Whiskers(R"(
+ function <functionName>(addr, function_id, pos) {
+ mstore(pos, <combineExtFun>(addr, function_id))
+ }
+ )")
+ ("functionName", functionName)
+ ("combineExtFun", combineExternalFunctionIdFunction())
+ .render();
+ });
+ }
+}
+
+string ABIFunctions::copyToMemoryFunction(bool _fromCalldata)
+{
+ string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
+ return createFunction(functionName, [&]() {
+ if (_fromCalldata)
+ {
+ return Whiskers(R"(
+ function <functionName>(src, dst, length) {
+ calldatacopy(dst, src, length)
+ // clear end
+ mstore(add(dst, length), 0)
+ }
+ )")
+ ("functionName", functionName)
+ .render();
+ }
+ else
+ {
+ return Whiskers(R"(
+ function <functionName>(src, dst, length) {
+ let i := 0
+ for { } lt(i, length) { i := add(i, 32) }
+ {
+ mstore(add(dst, i), mload(add(src, i)))
+ }
+ switch eq(i, length)
+ case 0 {
+ // clear end
+ mstore(add(dst, length), 0)
+ }
+ }
+ )")
+ ("functionName", functionName)
+ .render();
+ }
+ });
+}
+
+string ABIFunctions::shiftLeftFunction(size_t _numBits)
+{
+ string functionName = "shift_left_" + to_string(_numBits);
+ return createFunction(functionName, [&]() {
+ solAssert(_numBits < 256, "");
+ return
+ Whiskers(R"(function <functionName>(value) -> newValue {
+ newValue := mul(value, <multiplier>)
+ })")
+ ("functionName", functionName)
+ ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
+ .render();
+ });
+}
+
+string ABIFunctions::shiftRightFunction(size_t _numBits, bool _signed)
+{
+ string functionName = "shift_right_" + to_string(_numBits) + (_signed ? "_signed" : "_unsigned");
+ return createFunction(functionName, [&]() {
+ solAssert(_numBits < 256, "");
+ return
+ Whiskers(R"(function <functionName>(value) -> newValue {
+ newValue := <div>(value, <multiplier>)
+ })")
+ ("functionName", functionName)
+ ("div", _signed ? "sdiv" : "div")
+ ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
+ .render();
+ });
+}
+
+string ABIFunctions::roundUpFunction()
+{
+ string functionName = "round_up_to_mul_of_32";
+ return createFunction(functionName, [&]() {
+ return
+ Whiskers(R"(function <functionName>(value) -> result {
+ result := and(add(value, 31), not(31))
+ })")
+ ("functionName", functionName)
+ .render();
+ });
+}
+
+string ABIFunctions::arrayLengthFunction(ArrayType const& _type)
+{
+ string functionName = "array_length_" + _type.identifier();
+ return createFunction(functionName, [&]() {
+ Whiskers w(R"(
+ function <functionName>(value) -> length {
+ <body>
+ }
+ )");
+ w("functionName", functionName);
+ string body;
+ if (!_type.isDynamicallySized())
+ body = "length := " + toCompactHexWithPrefix(_type.length());
+ else
+ {
+ switch (_type.location())
+ {
+ case DataLocation::CallData:
+ solAssert(false, "called regular array length function on calldata array");
+ break;
+ case DataLocation::Memory:
+ body = "length := mload(value)";
+ break;
+ case DataLocation::Storage:
+ if (_type.isByteArray())
+ {
+ // Retrieve length both for in-place strings and off-place strings:
+ // Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2
+ // i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it
+ // computes (x & (-1)) / 2, which is equivalent to just x / 2.
+ body = R"(
+ length := sload(value)
+ let mask := sub(mul(0x100, iszero(and(length, 1))), 1)
+ length := div(and(length, mask), 2)
+ )";
+ }
+ else
+ body = "length := sload(value)";
+ break;
+ }
+ }
+ solAssert(!body.empty(), "");
+ w("body", body);
+ return w.render();
+ });
+}
+
+string ABIFunctions::arrayDataAreaFunction(ArrayType const& _type)
+{
+ string functionName = "array_dataslot_" + _type.identifier();
+ return createFunction(functionName, [&]() {
+ if (_type.dataStoredIn(DataLocation::Memory))
+ {
+ if (_type.isDynamicallySized())
+ return Whiskers(R"(
+ function <functionName>(memPtr) -> dataPtr {
+ dataPtr := add(memPtr, 0x20)
+ }
+ )")
+ ("functionName", functionName)
+ .render();
+ else
+ return Whiskers(R"(
+ function <functionName>(memPtr) -> dataPtr {
+ dataPtr := memPtr
+ }
+ )")
+ ("functionName", functionName)
+ .render();
+ }
+ else if (_type.dataStoredIn(DataLocation::Storage))
+ {
+ if (_type.isDynamicallySized())
+ {
+ Whiskers w(R"(
+ function <functionName>(slot) -> dataSlot {
+ mstore(0, slot)
+ dataSlot := keccak256(0, 0x20)
+ }
+ )");
+ w("functionName", functionName);
+ return w.render();
+ }
+ else
+ {
+ Whiskers w(R"(
+ function <functionName>(slot) -> dataSlot {
+ dataSlot := slot
+ }
+ )");
+ w("functionName", functionName);
+ return w.render();
+ }
+ }
+ else
+ {
+ // Not used for calldata
+ solAssert(false, "");
+ }
+ });
+}
+
+string ABIFunctions::nextArrayElementFunction(ArrayType const& _type)
+{
+ solAssert(!_type.isByteArray(), "");
+ solAssert(
+ _type.location() == DataLocation::Memory ||
+ _type.location() == DataLocation::Storage,
+ ""
+ );
+ solAssert(
+ _type.location() == DataLocation::Memory ||
+ _type.baseType()->storageBytes() > 16,
+ ""
+ );
+ string functionName = "array_nextElement_" + _type.identifier();
+ return createFunction(functionName, [&]() {
+ if (_type.location() == DataLocation::Memory)
+ return Whiskers(R"(
+ function <functionName>(memPtr) -> nextPtr {
+ nextPtr := add(memPtr, 0x20)
+ }
+ )")
+ ("functionName", functionName)
+ .render();
+ else if (_type.location() == DataLocation::Storage)
+ return Whiskers(R"(
+ function <functionName>(slot) -> nextSlot {
+ nextSlot := add(slot, 1)
+ }
+ )")
+ ("functionName", functionName)
+ .render();
+ else
+ solAssert(false, "");
+ });
+}
+
+string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator)
+{
+ if (!m_requestedFunctions.count(_name))
+ {
+ auto fun = _creator();
+ solAssert(!fun.empty(), "");
+ m_requestedFunctions[_name] = fun;
+ }
+ return _name;
+}
+
+size_t ABIFunctions::headSize(TypePointers const& _targetTypes)
+{
+ size_t headSize = 0;
+ for (auto const& t: _targetTypes)
+ {
+ if (t->isDynamicallyEncoded())
+ headSize += 0x20;
+ else
+ {
+ solAssert(t->calldataEncodedSize() > 0, "");
+ headSize += t->calldataEncodedSize();
+ }
+ }
+
+ return headSize;
+}
diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h
new file mode 100644
index 00000000..e43e2323
--- /dev/null
+++ b/libsolidity/codegen/ABIFunctions.h
@@ -0,0 +1,172 @@
+/*
+ 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/>.
+*/
+/**
+ * @author Christian <chris@ethereum.org>
+ * @date 2017
+ * Routines that generate JULIA code related to ABI encoding, decoding and type conversions.
+ */
+
+#pragma once
+
+#include <libsolidity/ast/ASTForward.h>
+
+#include <vector>
+#include <functional>
+#include <map>
+
+namespace dev {
+namespace solidity {
+
+class Type;
+class ArrayType;
+class StructType;
+class FunctionType;
+using TypePointer = std::shared_ptr<Type const>;
+using TypePointers = std::vector<TypePointer>;
+
+///
+/// Class to generate encoding and decoding functions. Also maintains a collection
+/// of "functions to be generated" in order to avoid generating the same function
+/// multiple times.
+///
+/// Make sure to include the result of ``requestedFunctions()`` to a block that
+/// is visible from the code that was generated here.
+class ABIFunctions
+{
+public:
+ ~ABIFunctions();
+
+ /// @returns assembly code block to ABI-encode values of @a _givenTypes residing on the stack
+ /// into memory, converting the types to @a _targetTypes on the fly.
+ /// Assumed variables to be present: <$value0> <$value1> ... <$value(n-1)> <$headStart>
+ /// Does not allocate memory (does not change the memory head pointer), but writes
+ /// to memory starting at $headStart and an unrestricted amount after that.
+ /// Assigns the end of encoded memory either to $value0 or (if that is not present)
+ /// to $headStart.
+ std::string tupleEncoder(
+ TypePointers const& _givenTypes,
+ TypePointers const& _targetTypes,
+ bool _encodeAsLibraryTypes = false
+ );
+
+ /// @returns auxiliary functions referenced from the block generated in @a tupleEncoder
+ std::string requestedFunctions();
+
+private:
+ /// @returns the name of the cleanup function for the given type and
+ /// adds its implementation to the requested functions.
+ /// @param _revertOnFailure if true, causes revert on invalid data,
+ /// otherwise an assertion failure.
+ std::string cleanupFunction(Type const& _type, bool _revertOnFailure = false);
+
+ /// @returns the name of the function that converts a value of type @a _from
+ /// to a value of type @a _to. The resulting vale is guaranteed to be in range
+ /// (i.e. "clean"). Asserts on failure.
+ std::string conversionFunction(Type const& _from, Type const& _to);
+
+ std::string cleanupCombinedExternalFunctionIdFunction();
+
+ /// @returns a function that combines the address and selector to a single value
+ /// for use in the ABI.
+ std::string combineExternalFunctionIdFunction();
+
+ /// @returns the name of the ABI encoding function with the given type
+ /// and queues the generation of the function to the requested functions.
+ /// @param _compacted if true, the input value was just loaded from storage
+ /// or memory and thus might be compacted into a single slot (depending on the type).
+ std::string abiEncodingFunction(
+ Type const& _givenType,
+ Type const& _targetType,
+ bool _encodeAsLibraryTypes,
+ bool _compacted
+ );
+ /// Part of @a abiEncodingFunction for array target type and given calldata array.
+ std::string abiEncodingFunctionCalldataArray(
+ Type const& _givenType,
+ Type const& _targetType,
+ bool _encodeAsLibraryTypes
+ );
+ /// Part of @a abiEncodingFunction for array target type and given memory array or
+ /// a given storage array with one item per slot.
+ std::string abiEncodingFunctionSimpleArray(
+ ArrayType const& _givenType,
+ ArrayType const& _targetType,
+ bool _encodeAsLibraryTypes
+ );
+ std::string abiEncodingFunctionMemoryByteArray(
+ ArrayType const& _givenType,
+ ArrayType const& _targetType,
+ bool _encodeAsLibraryTypes
+ );
+ /// Part of @a abiEncodingFunction for array target type and given storage array
+ /// where multiple items are packed into the same storage slot.
+ std::string abiEncodingFunctionCompactStorageArray(
+ ArrayType const& _givenType,
+ ArrayType const& _targetType,
+ bool _encodeAsLibraryTypes
+ );
+
+ // @returns the name of the ABI encoding function with the given type
+ // and queues the generation of the function to the requested functions.
+ // Case for _givenType being a string literal
+ std::string abiEncodingFunctionStringLiteral(
+ Type const& _givenType,
+ Type const& _targetType,
+ bool _encodeAsLibraryTypes
+ );
+
+ std::string abiEncodingFunctionFunctionType(
+ FunctionType const& _from,
+ Type const& _to,
+ bool _encodeAsLibraryTypes,
+ bool _compacted
+ );
+
+ /// @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.
+ std::string copyToMemoryFunction(bool _fromCalldata);
+
+ std::string shiftLeftFunction(size_t _numBits);
+ std::string shiftRightFunction(size_t _numBits, bool _signed);
+ /// @returns the name of a function that rounds its input to the next multiple
+ /// of 32 or the input if it is a multiple of 32.
+ std::string roundUpFunction();
+
+ std::string arrayLengthFunction(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.
+ std::string arrayDataAreaFunction(ArrayType const& _type);
+ /// @returns the name of a function that advances an array data pointer to the next element.
+ /// Only works for memory arrays and storage arrays that store one item per slot.
+ std::string nextArrayElementFunction(ArrayType const& _type);
+
+ /// 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.
+ std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator);
+
+ /// @returns the size of the static part of the encoding of the given types.
+ static size_t headSize(TypePointers const& _targetTypes);
+
+ /// Map from function name to code for a multi-use function.
+ std::map<std::string, std::string> m_requestedFunctions;
+};
+
+}
+}
diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h
index 806fbea7..f3ddc4ee 100644
--- a/libsolidity/codegen/ArrayUtils.h
+++ b/libsolidity/codegen/ArrayUtils.h
@@ -40,7 +40,7 @@ using TypePointer = std::shared_ptr<Type const>;
class ArrayUtils
{
public:
- ArrayUtils(CompilerContext& _context): m_context(_context) {}
+ explicit ArrayUtils(CompilerContext& _context): m_context(_context) {}
/// Copies an array to an array in storage. The arrays can be of different types only if
/// their storage representation is the same.
diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h
index eef078c1..8c63ea9c 100644
--- a/libsolidity/codegen/Compiler.h
+++ b/libsolidity/codegen/Compiler.h
@@ -51,9 +51,9 @@ public:
ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts
);
- eth::Assembly const& assembly() { return m_context.assembly(); }
- eth::LinkerObject assembledObject() { return m_context.assembledObject(); }
- eth::LinkerObject runtimeObject() { return m_context.assembledRuntimeObject(m_runtimeSub); }
+ eth::Assembly const& assembly() const { return m_context.assembly(); }
+ eth::LinkerObject assembledObject() const { return m_context.assembledObject(); }
+ eth::LinkerObject runtimeObject() const { return m_context.assembledRuntimeObject(m_runtimeSub); }
/// @arg _sourceCodes is the map of input files to source code strings
/// @arg _inJsonFromat shows whether the out should be in Json format
Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const
diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp
index bc4de3ee..ed780d0b 100644
--- a/libsolidity/codegen/CompilerContext.cpp
+++ b/libsolidity/codegen/CompilerContext.cpp
@@ -44,11 +44,6 @@ namespace dev
namespace solidity
{
-void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaration)
-{
- m_magicGlobals.insert(&_declaration);
-}
-
void CompilerContext::addStateVariable(
VariableDeclaration const& _declaration,
u256 const& _storageOffset,
diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h
index 13821f67..96cbf6c1 100644
--- a/libsolidity/codegen/CompilerContext.h
+++ b/libsolidity/codegen/CompilerContext.h
@@ -48,7 +48,7 @@ namespace solidity {
class CompilerContext
{
public:
- CompilerContext(CompilerContext* _runtimeContext = nullptr):
+ explicit CompilerContext(CompilerContext* _runtimeContext = nullptr):
m_asm(std::make_shared<eth::Assembly>()),
m_runtimeContext(_runtimeContext)
{
@@ -56,7 +56,9 @@ public:
m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data());
}
- void addMagicGlobal(MagicVariableDeclaration const& _declaration);
+ void setExperimentalFeatures(std::set<ExperimentalFeature> const& _features) { m_experimentalFeatures = _features; }
+ bool experimentalFeatureActive(ExperimentalFeature _feature) const { return m_experimentalFeatures.count(_feature); }
+
void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0);
void removeVariable(VariableDeclaration const& _declaration);
@@ -68,7 +70,6 @@ public:
void adjustStackOffset(int _adjustment) { m_asm->adjustDeposit(_adjustment); }
unsigned stackHeight() const { solAssert(m_asm->deposit() >= 0, ""); return unsigned(m_asm->deposit()); }
- bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration) != 0; }
bool isLocalVariable(Declaration const* _declaration) const;
bool isStateVariable(Declaration const* _declaration) const { return m_stateVariables.count(_declaration) != 0; }
@@ -207,8 +208,8 @@ public:
return m_asm->stream(_stream, "", _sourceCodes, _inJsonFormat);
}
- eth::LinkerObject const& assembledObject() { return m_asm->assemble(); }
- eth::LinkerObject const& assembledRuntimeObject(size_t _subIndex) { return m_asm->sub(_subIndex).assemble(); }
+ eth::LinkerObject const& assembledObject() const { return m_asm->assemble(); }
+ eth::LinkerObject const& assembledRuntimeObject(size_t _subIndex) const { return m_asm->sub(_subIndex).assemble(); }
/**
* Helper class to pop the visited nodes stack when a scope closes
@@ -265,8 +266,8 @@ private:
} m_functionCompilationQueue;
eth::AssemblyPointer m_asm;
- /// Magic global variables like msg, tx or this, distinguished by type.
- std::set<Declaration const*> m_magicGlobals;
+ /// Activated experimental features.
+ std::set<ExperimentalFeature> m_experimentalFeatures;
/// Other already compiled contracts to be used in contract creation calls.
std::map<ContractDefinition const*, eth::Assembly const*> m_compiledContracts;
/// Storage offsets of state variables
diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp
index 782aad9d..a0fc5d55 100644
--- a/libsolidity/codegen/CompilerUtils.cpp
+++ b/libsolidity/codegen/CompilerUtils.cpp
@@ -25,6 +25,7 @@
#include <libevmasm/Instruction.h>
#include <libsolidity/codegen/ArrayUtils.h>
#include <libsolidity/codegen/LValue.h>
+#include <libsolidity/codegen/ABIFunctions.h>
using namespace std;
@@ -182,6 +183,18 @@ void CompilerUtils::encodeToMemory(
if (_givenTypes.empty())
return;
+ else if (
+ _padToWordBoundaries &&
+ !_copyDynamicDataInPlace &&
+ m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)
+ )
+ {
+ // Use the new JULIA-based encoding function
+ auto stackHeightBefore = m_context.stackHeight();
+ abiEncode(_givenTypes, targetTypes, _encodeAsLibraryTypes);
+ solAssert(stackHeightBefore - m_context.stackHeight() == sizeOnStack(_givenTypes), "");
+ return;
+ }
// Stack during operation:
// <v1> <v2> ... <vn> <mem_start> <dyn_head_1> ... <dyn_head_r> <end_of_mem>
@@ -289,6 +302,28 @@ void CompilerUtils::encodeToMemory(
popStackSlots(argSize + dynPointers + 1);
}
+void CompilerUtils::abiEncode(
+ TypePointers const& _givenTypes,
+ TypePointers const& _targetTypes,
+ bool _encodeAsLibraryTypes
+)
+{
+ // stack: <$value0> <$value1> ... <$value(n-1)> <$headStart>
+
+ vector<string> variables;
+ size_t numValues = sizeOnStack(_givenTypes);
+ for (size_t i = 0; i < numValues; ++i)
+ variables.push_back("$value" + to_string(i));
+ variables.push_back("$headStart");
+
+ ABIFunctions funs;
+ string routine = funs.tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes);
+ routine += funs.requestedFunctions();
+ m_context.appendInlineAssembly("{" + routine + "}", variables);
+ // Remove everyhing except for "value0" / the final memory pointer.
+ popStackSlots(numValues);
+}
+
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
{
auto repeat = m_context.newTag();
diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h
index fb169463..18b70250 100644
--- a/libsolidity/codegen/CompilerUtils.h
+++ b/libsolidity/codegen/CompilerUtils.h
@@ -33,7 +33,7 @@ class Type; // forward
class CompilerUtils
{
public:
- CompilerUtils(CompilerContext& _context): m_context(_context) {}
+ explicit CompilerUtils(CompilerContext& _context): m_context(_context) {}
/// Stores the initial value of the free-memory-pointer at its position;
void initialiseFreeMemoryPointer();
@@ -103,6 +103,14 @@ public:
bool _encodeAsLibraryTypes = false
);
+ /// Special case of @a encodeToMemory which assumes that everything is padded to words
+ /// and dynamic data is not copied in place (i.e. a proper ABI encoding).
+ void abiEncode(
+ TypePointers const& _givenTypes,
+ TypePointers const& _targetTypes,
+ bool _encodeAsLibraryTypes = 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 fd0998d4..e53f1b94 100644
--- a/libsolidity/codegen/ContractCompiler.cpp
+++ b/libsolidity/codegen/ContractCompiler.cpp
@@ -45,7 +45,7 @@ using namespace dev::solidity;
class StackHeightChecker
{
public:
- StackHeightChecker(CompilerContext const& _context):
+ explicit StackHeightChecker(CompilerContext const& _context):
m_context(_context), stackHeight(m_context.stackHeight()) {}
void check() { solAssert(m_context.stackHeight() == stackHeight, std::string("I sense a disturbance in the stack: ") + std::to_string(m_context.stackHeight()) + " vs " + std::to_string(stackHeight)); }
private:
@@ -100,6 +100,7 @@ void ContractCompiler::initializeContext(
map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts
)
{
+ m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures);
m_context.setCompiledContracts(_compiledContracts);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
CompilerUtils(m_context).initialiseFreeMemoryPointer();
diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp
index 521d485f..639bfc32 100644
--- a/libsolidity/codegen/ExpressionCompiler.cpp
+++ b/libsolidity/codegen/ExpressionCompiler.cpp
@@ -645,8 +645,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
FunctionType::Kind::BareCall,
false,
nullptr,
- false,
- false,
+ StateMutability::NonPayable,
true,
true
),
@@ -1812,7 +1811,7 @@ void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression)
setLValue<StorageItem>(_expression, *_expression.annotation().type);
}
-bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token::Value _op)
+bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token::Value _op) const
{
if (Token::isCompareOp(_op) || Token::isShiftOp(_op))
return true;
diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h
index 3b8cf1c6..5f6c3d64 100644
--- a/libsolidity/codegen/ExpressionCompiler.h
+++ b/libsolidity/codegen/ExpressionCompiler.h
@@ -119,7 +119,7 @@ private:
/// @returns true if the operator applied to the given type requires a cleanup prior to the
/// operation.
- bool cleanupNeededForOp(Type::Category _type, Token::Value _op);
+ bool cleanupNeededForOp(Type::Category _type, Token::Value _op) const;
/// @returns the CompilerUtils object containing the current context.
CompilerUtils utils();
diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp
new file mode 100644
index 00000000..fd78e578
--- /dev/null
+++ b/libsolidity/formal/SMTChecker.cpp
@@ -0,0 +1,588 @@
+/*
+ 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/SMTChecker.h>
+
+#ifdef HAVE_Z3
+#include <libsolidity/formal/Z3Interface.h>
+#else
+#include <libsolidity/formal/SMTLib2Interface.h>
+#endif
+
+#include <libsolidity/interface/ErrorReporter.h>
+
+#include <boost/range/adaptor/map.hpp>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+SMTChecker::SMTChecker(ErrorReporter& _errorReporter, ReadCallback::Callback const& _readFileCallback):
+#ifdef HAVE_Z3
+ m_interface(make_shared<smt::Z3Interface>()),
+#else
+ m_interface(make_shared<smt::SMTLib2Interface>(_readFileCallback)),
+#endif
+ m_errorReporter(_errorReporter)
+{
+ (void)_readFileCallback;
+}
+
+void SMTChecker::analyze(SourceUnit const& _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);
+}
+
+bool SMTChecker::visit(FunctionDefinition const& _function)
+{
+ if (!_function.modifiers().empty() || _function.isConstructor())
+ m_errorReporter.warning(
+ _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();
+ 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.
+ // If we add storage variables, those should be cleared differently.
+ m_currentSequenceCounter.clear();
+ m_nextFreeSequenceCounter.clear();
+ m_interface->pop();
+ m_currentFunction = nullptr;
+}
+
+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();
+
+ decltype(m_currentSequenceCounter) countersAtEndOfFalse;
+ 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();
+ }
+ else
+ countersAtEndOfFalse = countersAtStart;
+
+ // Reset all values that have been touched.
+
+ // TODO this should use a previously generated side-effect structure
+
+ solAssert(countersAtEndOfFalse.size() == countersAtEndOfTrue.size(), "");
+ for (auto const& declCounter: countersAtEndOfTrue)
+ {
+ 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);
+ }
+ return false;
+}
+
+bool SMTChecker::visit(WhileStatement const& _node)
+{
+ _node.condition().accept(*this);
+
+ //m_interface->push();
+ //m_interface->addAssertion(expr(_node.condition()));
+ // TDOO clear knowledge (increment sequence numbers and add bounds assertions ) apart from assertions
+
+ // TODO combine similar to if
+ return true;
+}
+
+void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl)
+{
+ if (_varDecl.declarations().size() != 1)
+ m_errorReporter.warning(
+ _varDecl.location(),
+ "Assertion checker does not yet support such variable declarations."
+ );
+ 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()));
+ }
+ else
+ m_errorReporter.warning(
+ _varDecl.location(),
+ "Assertion checker does not yet implement such variable declarations."
+ );
+}
+
+void SMTChecker::endVisit(ExpressionStatement const&)
+{
+}
+
+void SMTChecker::endVisit(Assignment const& _assignment)
+{
+ if (_assignment.assignmentOperator() != Token::Value::Assign)
+ m_errorReporter.warning(
+ _assignment.location(),
+ "Assertion checker does not yet implement compound assignment."
+ );
+ else if (_assignment.annotation().type->category() != Type::Category::Integer)
+ m_errorReporter.warning(
+ _assignment.location(),
+ "Assertion checker does not yet implement type " + _assignment.annotation().type->toString()
+ );
+ else if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_assignment.leftHandSide()))
+ {
+ 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()));
+ else
+ m_errorReporter.warning(
+ _assignment.location(),
+ "Assertion checker does not yet implement such assignments."
+ );
+ }
+ else
+ m_errorReporter.warning(
+ _assignment.location(),
+ "Assertion checker does not yet implement such assignments."
+ );
+}
+
+void SMTChecker::endVisit(TupleExpression const& _tuple)
+{
+ if (_tuple.isInlineArray() || _tuple.components().size() != 1)
+ m_errorReporter.warning(
+ _tuple.location(),
+ "Assertion checker does not yet implement tules and inline arrays."
+ );
+ else
+ m_interface->addAssertion(expr(_tuple) == expr(*_tuple.components()[0]));
+}
+
+void SMTChecker::endVisit(BinaryOperation const& _op)
+{
+ if (Token::isArithmeticOp(_op.getOperator()))
+ arithmeticOperation(_op);
+ else if (Token::isCompareOp(_op.getOperator()))
+ compareOperation(_op);
+ else if (Token::isBooleanOp(_op.getOperator()))
+ booleanOperation(_op);
+ else
+ m_errorReporter.warning(
+ _op.location(),
+ "Assertion checker does not yet implement this operator."
+ );
+}
+
+void SMTChecker::endVisit(FunctionCall const& _funCall)
+{
+ FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
+
+ std::vector<ASTPointer<Expression const>> const args = _funCall.arguments();
+ if (funType.kind() == FunctionType::Kind::Assert)
+ {
+ 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]));
+ }
+ 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.
+ }
+}
+
+void SMTChecker::endVisit(Identifier const& _identifier)
+{
+ Declaration const* decl = _identifier.annotation().referencedDeclaration;
+ solAssert(decl, "");
+ if (dynamic_cast<IntegerType const*>(_identifier.annotation().type.get()))
+ {
+ m_interface->addAssertion(expr(_identifier) == currentValue(*decl));
+ return;
+ }
+ 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)
+{
+ Type const& type = *_literal.annotation().type;
+ if (type.category() == Type::Category::Integer || type.category() == Type::Category::RationalNumber)
+ {
+ if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&type))
+ solAssert(!rational->isFractional(), "");
+
+ m_interface->addAssertion(expr(_literal) == smt::Expression(type.literalValue(&_literal)));
+ }
+ else
+ m_errorReporter.warning(
+ _literal.location(),
+ "Assertion checker does not yet support the type of this expression (" +
+ _literal.annotation().type->toString() +
+ ")."
+ );
+}
+
+void SMTChecker::arithmeticOperation(BinaryOperation const& _op)
+{
+ switch (_op.getOperator())
+ {
+ case Token::Add:
+ case Token::Sub:
+ case Token::Mul:
+ {
+ solAssert(_op.annotation().commonType, "");
+ solAssert(_op.annotation().commonType->category() == Type::Category::Integer, "");
+ 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::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
+ );
+
+ m_interface->addAssertion(expr(_op) == value);
+ break;
+ }
+ default:
+ m_errorReporter.warning(
+ _op.location(),
+ "Assertion checker does not yet implement this operator."
+ );
+ }
+}
+
+void SMTChecker::compareOperation(BinaryOperation const& _op)
+{
+ solAssert(_op.annotation().commonType, "");
+ if (_op.annotation().commonType->category() == Type::Category::Integer)
+ {
+ smt::Expression left(expr(_op.leftExpression()));
+ smt::Expression right(expr(_op.rightExpression()));
+ Token::Value op = _op.getOperator();
+ smt::Expression value = (
+ op == Token::Equal ? (left == right) :
+ op == Token::NotEqual ? (left != right) :
+ op == Token::LessThan ? (left < right) :
+ op == Token::LessThanOrEqual ? (left <= right) :
+ op == Token::GreaterThan ? (left > right) :
+ /*op == Token::GreaterThanOrEqual*/ (left >= right)
+ );
+ // TODO: check that other values for op are not possible.
+ m_interface->addAssertion(expr(_op) == value);
+ }
+ else
+ m_errorReporter.warning(
+ _op.location(),
+ "Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for comparisons"
+ );
+}
+
+void SMTChecker::booleanOperation(BinaryOperation const& _op)
+{
+ solAssert(_op.getOperator() == Token::And || _op.getOperator() == Token::Or, "");
+ solAssert(_op.annotation().commonType, "");
+ if (_op.annotation().commonType->category() == Type::Category::Bool)
+ {
+ if (_op.getOperator() == Token::And)
+ m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) && expr(_op.rightExpression()));
+ else
+ m_interface->addAssertion(expr(_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"
+ );
+}
+
+void SMTChecker::checkCondition(
+ smt::Expression _condition,
+ SourceLocation const& _location,
+ string const& _description,
+ string const& _additionalValueName,
+ smt::Expression* _additionalValue
+)
+{
+ m_interface->push();
+ m_interface->addAssertion(_condition);
+
+ vector<smt::Expression> expressionsToEvaluate;
+ vector<string> expressionNames;
+ if (m_currentFunction)
+ {
+ if (_additionalValue)
+ {
+ expressionsToEvaluate.emplace_back(*_additionalValue);
+ expressionNames.push_back(_additionalValueName);
+ }
+ for (auto const& param: m_currentFunction->parameters())
+ if (knownVariable(*param))
+ {
+ expressionsToEvaluate.emplace_back(currentValue(*param));
+ expressionNames.push_back(param->name());
+ }
+ for (auto const& var: m_currentFunction->localVariables())
+ if (knownVariable(*var))
+ {
+ expressionsToEvaluate.emplace_back(currentValue(*var));
+ expressionNames.push_back(var->name());
+ }
+ }
+ 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;
+ }
+
+ switch (result)
+ {
+ case smt::CheckResult::SATISFIABLE:
+ {
+ std::ostringstream message;
+ message << _description << " happens here";
+ if (m_currentFunction)
+ {
+ 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";
+ }
+ }
+ else
+ message << ".";
+ m_errorReporter.warning(_location, message.str());
+ break;
+ }
+ case smt::CheckResult::UNSATISFIABLE:
+ break;
+ case smt::CheckResult::UNKNOWN:
+ m_errorReporter.warning(_location, _description + " might happen here.");
+ break;
+ case smt::CheckResult::ERROR:
+ m_errorReporter.warning(_location, "Error trying to invoke SMT solver.");
+ break;
+ default:
+ solAssert(false, "");
+ }
+ m_interface->pop();
+}
+
+void SMTChecker::createVariable(VariableDeclaration const& _varDecl, bool _setToZero)
+{
+ if (dynamic_cast<IntegerType const*>(_varDecl.type().get()))
+ {
+ solAssert(m_currentSequenceCounter.count(&_varDecl) == 0, "");
+ solAssert(m_nextFreeSequenceCounter.count(&_varDecl) == 0, "");
+ solAssert(m_Variables.count(&_varDecl) == 0, "");
+ 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);
+ }
+ else
+ m_errorReporter.warning(
+ _varDecl.location(),
+ "Assertion checker does not yet support the type of this variable."
+ );
+}
+
+string SMTChecker::uniqueSymbol(Declaration const& _decl)
+{
+ return _decl.name() + "_" + to_string(_decl.id());
+}
+
+string SMTChecker::uniqueSymbol(Expression const& _expr)
+{
+ return "expr_" + to_string(_expr.id());
+}
+
+bool SMTChecker::knownVariable(Declaration const& _decl)
+{
+ return m_currentSequenceCounter.count(&_decl);
+}
+
+smt::Expression SMTChecker::currentValue(Declaration const& _decl)
+{
+ solAssert(m_currentSequenceCounter.count(&_decl), "");
+ return valueAtSequence(_decl, m_currentSequenceCounter.at(&_decl));
+}
+
+smt::Expression SMTChecker::valueAtSequence(const Declaration& _decl, int _sequence)
+{
+ return var(_decl)(_sequence);
+}
+
+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)
+{
+ auto const& intType = dynamic_cast<IntegerType const&>(*_decl.type());
+
+ if (_setToZero)
+ m_interface->addAssertion(currentValue(_decl) == 0);
+ else
+ {
+ m_interface->addAssertion(currentValue(_decl) >= minValue(intType));
+ m_interface->addAssertion(currentValue(_decl) <= maxValue(intType));
+ }
+}
+
+smt::Expression SMTChecker::minValue(IntegerType const& _t)
+{
+ return smt::Expression(_t.minValue());
+}
+
+smt::Expression SMTChecker::maxValue(IntegerType const& _t)
+{
+ return smt::Expression(_t.maxValue());
+}
+
+smt::Expression SMTChecker::expr(Expression const& _e)
+{
+ if (!m_Expressions.count(&_e))
+ {
+ solAssert(_e.annotation().type, "");
+ switch (_e.annotation().type->category())
+ {
+ case Type::Category::RationalNumber:
+ {
+ if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(_e.annotation().type.get()))
+ solAssert(!rational->isFractional(), "");
+ m_Expressions.emplace(&_e, m_interface->newInteger(uniqueSymbol(_e)));
+ break;
+ }
+ case Type::Category::Integer:
+ m_Expressions.emplace(&_e, m_interface->newInteger(uniqueSymbol(_e)));
+ break;
+ case Type::Category::Bool:
+ m_Expressions.emplace(&_e, m_interface->newBool(uniqueSymbol(_e)));
+ break;
+ default:
+ solAssert(false, "Type not implemented.");
+ }
+ }
+ return m_Expressions.at(&_e);
+}
+
+smt::Expression SMTChecker::var(Declaration const& _decl)
+{
+ solAssert(m_Variables.count(&_decl), "");
+ return m_Variables.at(&_decl);
+}
diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h
new file mode 100644
index 00000000..d23fd201
--- /dev/null
+++ b/libsolidity/formal/SMTChecker.h
@@ -0,0 +1,114 @@
+/*
+ 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 <libsolidity/ast/ASTVisitor.h>
+#include <libsolidity/formal/SolverInterface.h>
+#include <libsolidity/interface/ReadFile.h>
+
+#include <map>
+#include <string>
+
+namespace dev
+{
+namespace solidity
+{
+
+class ErrorReporter;
+
+class SMTChecker: private ASTConstVisitor
+{
+public:
+ SMTChecker(ErrorReporter& _errorReporter, ReadCallback::Callback const& _readCallback);
+
+ void analyze(SourceUnit const& _sources);
+
+private:
+ // TODO: Check that we do not have concurrent reads and writes to a variable,
+ // because the order of expression evaluation is undefined
+ // TODO: or just force a certain order, but people might have a different idea about that.
+
+ virtual void endVisit(VariableDeclaration const& _node) override;
+ virtual bool visit(FunctionDefinition const& _node) override;
+ virtual void endVisit(FunctionDefinition const& _node) override;
+ virtual bool visit(IfStatement const& _node) override;
+ virtual bool visit(WhileStatement 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(BinaryOperation const& _node) override;
+ virtual void endVisit(FunctionCall const& _node) override;
+ virtual void endVisit(Identifier const& _node) override;
+ virtual void endVisit(Literal const& _node) override;
+
+ void arithmeticOperation(BinaryOperation const& _op);
+ void compareOperation(BinaryOperation const& _op);
+ void booleanOperation(BinaryOperation const& _op);
+
+ void checkCondition(
+ smt::Expression _condition,
+ SourceLocation const& _location,
+ std::string const& _description,
+ std::string const& _additionalValueName = "",
+ smt::Expression* _additionalValue = nullptr
+ );
+
+ void createVariable(VariableDeclaration const& _varDecl, bool _setToZero);
+
+ static std::string uniqueSymbol(Declaration const& _decl);
+ static std::string uniqueSymbol(Expression const& _expr);
+
+ /// @returns true if _delc is a variable that is known at the current point, i.e.
+ /// has a valid sequence number
+ bool knownVariable(Declaration const& _decl);
+ /// @returns an expression denoting the value of the variable declared in @a _decl
+ /// at the current point.
+ smt::Expression currentValue(Declaration const& _decl);
+ /// @returns an expression denoting the value of the variable declared in @a _decl
+ /// at the given sequence point. Does not ensure that this sequence point exists.
+ smt::Expression valueAtSequence(Declaration const& _decl, int _sequence);
+ /// Allocates a new sequence number for the declaration, updates the current
+ /// 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);
+
+ 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.
+ smt::Expression expr(Expression const& _e);
+ /// 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);
+
+ std::shared_ptr<smt::SolverInterface> m_interface;
+ 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;
+ ErrorReporter& m_errorReporter;
+
+ FunctionDefinition const* m_currentFunction = nullptr;
+};
+
+}
+}
diff --git a/libsolidity/formal/SMTLib2Interface.cpp b/libsolidity/formal/SMTLib2Interface.cpp
new file mode 100644
index 00000000..cbd766fb
--- /dev/null
+++ b/libsolidity/formal/SMTLib2Interface.cpp
@@ -0,0 +1,187 @@
+/*
+ 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/SMTLib2Interface.h>
+
+#include <libsolidity/interface/Exceptions.h>
+#include <libsolidity/interface/ReadFile.h>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/filesystem/operations.hpp>
+
+#include <cstdio>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <array>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+using namespace dev::solidity::smt;
+
+SMTLib2Interface::SMTLib2Interface(ReadCallback::Callback const& _queryCallback):
+ m_queryCallback(_queryCallback)
+{
+ reset();
+}
+
+void SMTLib2Interface::reset()
+{
+ m_accumulatedOutput.clear();
+ m_accumulatedOutput.emplace_back();
+ write("(set-option :produce-models true)");
+ write("(set-logic QF_UFLIA)");
+}
+
+void SMTLib2Interface::push()
+{
+ m_accumulatedOutput.emplace_back();
+}
+
+void SMTLib2Interface::pop()
+{
+ solAssert(!m_accumulatedOutput.empty(), "");
+ m_accumulatedOutput.pop_back();
+}
+
+Expression SMTLib2Interface::newFunction(string _name, Sort _domain, Sort _codomain)
+{
+ write(
+ "(declare-fun |" +
+ _name +
+ "| (" +
+ (_domain == Sort::Int ? "Int" : "Bool") +
+ ") " +
+ (_codomain == Sort::Int ? "Int" : "Bool") +
+ ")"
+ );
+ return SolverInterface::newFunction(move(_name), _domain, _codomain);
+}
+
+Expression SMTLib2Interface::newInteger(string _name)
+{
+ write("(declare-const |" + _name + "| Int)");
+ return SolverInterface::newInteger(move(_name));
+}
+
+Expression SMTLib2Interface::newBool(string _name)
+{
+ write("(declare-const |" + _name + "| Bool)");
+ return SolverInterface::newBool(std::move(_name));
+}
+
+void SMTLib2Interface::addAssertion(Expression const& _expr)
+{
+ write("(assert " + toSExpr(_expr) + ")");
+}
+
+pair<CheckResult, vector<string>> SMTLib2Interface::check(vector<Expression> const& _expressionsToEvaluate)
+{
+ string response = querySolver(
+ boost::algorithm::join(m_accumulatedOutput, "\n") +
+ checkSatAndGetValuesCommand(_expressionsToEvaluate)
+ );
+
+ CheckResult result;
+ // TODO proper parsing
+ if (boost::starts_with(response, "sat\n"))
+ result = CheckResult::SATISFIABLE;
+ else if (boost::starts_with(response, "unsat\n"))
+ result = CheckResult::UNSATISFIABLE;
+ else if (boost::starts_with(response, "unknown\n"))
+ result = CheckResult::UNKNOWN;
+ else
+ result = CheckResult::ERROR;
+
+ vector<string> values;
+ if (result != CheckResult::UNSATISFIABLE && result != CheckResult::ERROR)
+ values = parseValues(find(response.cbegin(), response.cend(), '\n'), response.cend());
+ return make_pair(result, values);
+}
+
+string SMTLib2Interface::toSExpr(Expression const& _expr)
+{
+ if (_expr.arguments.empty())
+ return _expr.name;
+ std::string sexpr = "(" + _expr.name;
+ for (auto const& arg: _expr.arguments)
+ sexpr += " " + toSExpr(arg);
+ sexpr += ")";
+ return sexpr;
+}
+
+void SMTLib2Interface::write(string _data)
+{
+ solAssert(!m_accumulatedOutput.empty(), "");
+ m_accumulatedOutput.back() += move(_data) + "\n";
+}
+
+string SMTLib2Interface::checkSatAndGetValuesCommand(vector<Expression> const& _expressionsToEvaluate)
+{
+ string command;
+ if (_expressionsToEvaluate.empty())
+ command = "(check-sat)\n";
+ else
+ {
+ // TODO make sure these are unique
+ for (size_t i = 0; i < _expressionsToEvaluate.size(); i++)
+ {
+ auto const& e = _expressionsToEvaluate.at(i);
+ // TODO they don't have to be ints...
+ command += "(declare-const |EVALEXPR_" + to_string(i) + "| Int)\n";
+ command += "(assert (= |EVALEXPR_" + to_string(i) + "| " + toSExpr(e) + "))\n";
+ }
+ command += "(check-sat)\n";
+ command += "(get-value (";
+ for (size_t i = 0; i < _expressionsToEvaluate.size(); i++)
+ command += "|EVALEXPR_" + to_string(i) + "| ";
+ command += "))\n";
+ }
+
+ return command;
+}
+
+vector<string> SMTLib2Interface::parseValues(string::const_iterator _start, string::const_iterator _end)
+{
+ vector<string> values;
+ while (_start < _end)
+ {
+ auto valStart = find(_start, _end, ' ');
+ if (valStart < _end)
+ ++valStart;
+ auto valEnd = find(valStart, _end, ')');
+ values.emplace_back(valStart, valEnd);
+ _start = find(valEnd, _end, '(');
+ }
+
+ return values;
+}
+
+string SMTLib2Interface::querySolver(string const& _input)
+{
+ if (!m_queryCallback)
+ BOOST_THROW_EXCEPTION(SolverError() << errinfo_comment("No SMT solver available."));
+
+ ReadCallback::Result queryResult = m_queryCallback(_input);
+ if (!queryResult.success)
+ BOOST_THROW_EXCEPTION(SolverError() << errinfo_comment(queryResult.responseOrErrorMessage));
+ return queryResult.responseOrErrorMessage;
+}
diff --git a/libsolidity/formal/SMTLib2Interface.h b/libsolidity/formal/SMTLib2Interface.h
new file mode 100644
index 00000000..b8dac366
--- /dev/null
+++ b/libsolidity/formal/SMTLib2Interface.h
@@ -0,0 +1,75 @@
+/*
+ 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 <libsolidity/formal/SolverInterface.h>
+
+#include <libsolidity/interface/Exceptions.h>
+#include <libsolidity/interface/ReadFile.h>
+
+#include <libdevcore/Common.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+#include <cstdio>
+
+namespace dev
+{
+namespace solidity
+{
+namespace smt
+{
+
+class SMTLib2Interface: public SolverInterface, public boost::noncopyable
+{
+public:
+ SMTLib2Interface(ReadCallback::Callback const& _queryCallback);
+
+ void reset() override;
+
+ void push() override;
+ void pop() override;
+
+ Expression newFunction(std::string _name, Sort _domain, Sort _codomain) override;
+ Expression newInteger(std::string _name) override;
+ Expression newBool(std::string _name) override;
+
+ void addAssertion(Expression const& _expr) override;
+ std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override;
+
+private:
+ std::string toSExpr(Expression const& _expr);
+
+ void write(std::string _data);
+
+ std::string checkSatAndGetValuesCommand(std::vector<Expression> const& _expressionsToEvaluate);
+ std::vector<std::string> parseValues(std::string::const_iterator _start, std::string::const_iterator _end);
+
+ /// Communicates with the solver via the callback. Throws SMTSolverError on error.
+ std::string querySolver(std::string const& _input);
+
+ ReadCallback::Callback m_queryCallback;
+ std::vector<std::string> m_accumulatedOutput;
+};
+
+}
+}
+}
diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h
new file mode 100644
index 00000000..32d92a2a
--- /dev/null
+++ b/libsolidity/formal/SolverInterface.h
@@ -0,0 +1,178 @@
+/*
+ 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 <libsolidity/interface/Exceptions.h>
+#include <libsolidity/interface/ReadFile.h>
+
+#include <libdevcore/Common.h>
+#include <libdevcore/Exceptions.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+#include <cstdio>
+
+namespace dev
+{
+namespace solidity
+{
+namespace smt
+{
+
+enum class CheckResult
+{
+ SATISFIABLE, UNSATISFIABLE, UNKNOWN, ERROR
+};
+
+enum class Sort
+{
+ Int, Bool
+};
+
+/// C++ representation of an SMTLIB2 expression.
+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()) {}
+
+ Expression(Expression const& _other) = default;
+ Expression(Expression&& _other) = default;
+ Expression& operator=(Expression const& _other) = default;
+ Expression& operator=(Expression&& _other) = default;
+
+ static Expression ite(Expression _condition, Expression _trueValue, Expression _falseValue)
+ {
+ return Expression("ite", std::vector<Expression>{
+ std::move(_condition), std::move(_trueValue), std::move(_falseValue)
+ });
+ }
+
+ friend Expression operator!(Expression _a)
+ {
+ return Expression("not", std::move(_a));
+ }
+ friend Expression operator&&(Expression _a, Expression _b)
+ {
+ return Expression("and", std::move(_a), std::move(_b));
+ }
+ friend Expression operator||(Expression _a, Expression _b)
+ {
+ return Expression("or", std::move(_a), std::move(_b));
+ }
+ friend Expression operator==(Expression _a, Expression _b)
+ {
+ return Expression("=", std::move(_a), std::move(_b));
+ }
+ friend Expression operator!=(Expression _a, Expression _b)
+ {
+ return !(std::move(_a) == std::move(_b));
+ }
+ friend Expression operator<(Expression _a, Expression _b)
+ {
+ return Expression("<", std::move(_a), std::move(_b));
+ }
+ friend Expression operator<=(Expression _a, Expression _b)
+ {
+ return Expression("<=", std::move(_a), std::move(_b));
+ }
+ friend Expression operator>(Expression _a, Expression _b)
+ {
+ return Expression(">", std::move(_a), std::move(_b));
+ }
+ friend Expression operator>=(Expression _a, Expression _b)
+ {
+ return Expression(">=", std::move(_a), std::move(_b));
+ }
+ friend Expression operator+(Expression _a, Expression _b)
+ {
+ return Expression("+", std::move(_a), std::move(_b));
+ }
+ friend Expression operator-(Expression _a, Expression _b)
+ {
+ return Expression("-", std::move(_a), std::move(_b));
+ }
+ friend Expression operator*(Expression _a, Expression _b)
+ {
+ return Expression("*", std::move(_a), std::move(_b));
+ }
+ Expression operator()(Expression _a) const
+ {
+ solAssert(arguments.empty(), "Attempted function application to non-function.");
+ return Expression(name, _a);
+ }
+
+ std::string const name;
+ std::vector<Expression> const arguments;
+
+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)}) {}
+};
+
+DEV_SIMPLE_EXCEPTION(SolverError);
+
+class SolverInterface
+{
+public:
+ virtual void reset() = 0;
+
+ virtual void push() = 0;
+ virtual void pop() = 0;
+
+ virtual Expression newFunction(std::string _name, Sort /*_domain*/, Sort /*_codomain*/)
+ {
+ // Subclasses should do something here
+ return Expression(std::move(_name), {});
+ }
+ virtual Expression newInteger(std::string _name)
+ {
+ // Subclasses should do something here
+ return Expression(std::move(_name), {});
+ }
+ virtual Expression newBool(std::string _name)
+ {
+ // Subclasses should do something here
+ return Expression(std::move(_name), {});
+ }
+
+ virtual void addAssertion(Expression const& _expr) = 0;
+
+ /// Checks for satisfiability, evaluates the expressions if a model
+ /// is available. Throws SMTSolverError on error.
+ virtual std::pair<CheckResult, std::vector<std::string>>
+ check(std::vector<Expression> const& _expressionsToEvaluate) = 0;
+};
+
+
+}
+}
+}
diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp
new file mode 100644
index 00000000..522928f0
--- /dev/null
+++ b/libsolidity/formal/Z3Interface.cpp
@@ -0,0 +1,189 @@
+/*
+ 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/Z3Interface.h>
+
+#include <libsolidity/interface/Exceptions.h>
+
+#include <libdevcore/CommonIO.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity::smt;
+
+Z3Interface::Z3Interface():
+ m_solver(m_context)
+{
+}
+
+void Z3Interface::reset()
+{
+ m_constants.clear();
+ m_functions.clear();
+ m_solver.reset();
+}
+
+void Z3Interface::push()
+{
+ m_solver.push();
+}
+
+void Z3Interface::pop()
+{
+ m_solver.pop();
+}
+
+Expression Z3Interface::newFunction(string _name, Sort _domain, Sort _codomain)
+{
+ m_functions.insert({_name, m_context.function(_name.c_str(), z3Sort(_domain), z3Sort(_codomain))});
+ return SolverInterface::newFunction(move(_name), _domain, _codomain);
+}
+
+Expression Z3Interface::newInteger(string _name)
+{
+ m_constants.insert({_name, m_context.int_const(_name.c_str())});
+ return SolverInterface::newInteger(move(_name));
+}
+
+Expression Z3Interface::newBool(string _name)
+{
+ m_constants.insert({_name, m_context.bool_const(_name.c_str())});
+ return SolverInterface::newBool(std::move(_name));
+}
+
+void Z3Interface::addAssertion(Expression const& _expr)
+{
+ m_solver.add(toZ3Expr(_expr));
+}
+
+pair<CheckResult, vector<string>> Z3Interface::check(vector<Expression> const& _expressionsToEvaluate)
+{
+// cout << "---------------------------------" << endl;
+// cout << m_solver << endl;
+ CheckResult result;
+ switch (m_solver.check())
+ {
+ case z3::check_result::sat:
+ result = CheckResult::SATISFIABLE;
+ cout << "sat" << endl;
+ break;
+ case z3::check_result::unsat:
+ result = CheckResult::UNSATISFIABLE;
+ cout << "unsat" << endl;
+ break;
+ case z3::check_result::unknown:
+ result = CheckResult::UNKNOWN;
+ cout << "unknown" << endl;
+ break;
+ default:
+ solAssert(false, "");
+ }
+// cout << "---------------------------------" << endl;
+
+
+ vector<string> values;
+ if (result != CheckResult::UNSATISFIABLE)
+ {
+ z3::model m = m_solver.get_model();
+ for (Expression const& e: _expressionsToEvaluate)
+ values.push_back(toString(m.eval(toZ3Expr(e))));
+ }
+ return make_pair(result, values);
+}
+
+z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
+{
+ if (_expr.arguments.empty() && m_constants.count(_expr.name))
+ return m_constants.at(_expr.name);
+ z3::expr_vector arguments(m_context);
+ for (auto const& arg: _expr.arguments)
+ arguments.push_back(toZ3Expr(arg));
+
+ static map<string, unsigned> arity{
+ {"ite", 3},
+ {"not", 1},
+ {"and", 2},
+ {"or", 2},
+ {"=", 2},
+ {"<", 2},
+ {"<=", 2},
+ {">", 2},
+ {">=", 2},
+ {"+", 2},
+ {"-", 2},
+ {"*", 2},
+ {">=", 2}
+ };
+ string const& n = _expr.name;
+ if (m_functions.count(n))
+ return m_functions.at(n)(arguments);
+ else if (m_constants.count(n))
+ {
+ solAssert(arguments.empty(), "");
+ return m_constants.at(n);
+ }
+ else if (arguments.empty())
+ {
+ // We assume it is an integer...
+ return m_context.int_val(n.c_str());
+ }
+
+ assert(arity.count(n) && arity.at(n) == arguments.size());
+ if (n == "ite")
+ return z3::ite(arguments[0], arguments[1], arguments[2]);
+ else if (n == "not")
+ return !arguments[0];
+ else if (n == "and")
+ return arguments[0] && arguments[1];
+ else if (n == "or")
+ return arguments[0] || arguments[1];
+ else if (n == "=")
+ return arguments[0] == arguments[1];
+ else if (n == "<")
+ return arguments[0] < arguments[1];
+ else if (n == "<=")
+ return arguments[0] <= arguments[1];
+ else if (n == ">")
+ return arguments[0] > arguments[1];
+ else if (n == ">=")
+ return arguments[0] >= arguments[1];
+ else if (n == "+")
+ 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];
+}
+
+z3::sort Z3Interface::z3Sort(Sort _sort)
+{
+ switch (_sort)
+ {
+ case Sort::Bool:
+ return m_context.bool_sort();
+ case Sort::Int:
+ return m_context.int_sort();
+ default:
+ break;
+ }
+ solAssert(false, "");
+ // Cannot be reached.
+ return m_context.int_sort();
+}
diff --git a/libsolidity/formal/Z3Interface.h b/libsolidity/formal/Z3Interface.h
new file mode 100644
index 00000000..44d4bb2f
--- /dev/null
+++ b/libsolidity/formal/Z3Interface.h
@@ -0,0 +1,65 @@
+/*
+ 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 <libsolidity/formal/SolverInterface.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <z3++.h>
+
+namespace dev
+{
+namespace solidity
+{
+namespace smt
+{
+
+class Z3Interface: public SolverInterface, public boost::noncopyable
+{
+public:
+ Z3Interface();
+
+ void reset() override;
+
+ void push() override;
+ void pop() override;
+
+ Expression newFunction(std::string _name, Sort _domain, Sort _codomain) override;
+ Expression newInteger(std::string _name) override;
+ Expression newBool(std::string _name) override;
+
+ void addAssertion(Expression const& _expr) override;
+ std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override;
+
+private:
+ z3::expr toZ3Expr(Expression const& _expr);
+ z3::sort z3Sort(smt::Sort _sort);
+
+ std::string checkSatAndGetValuesCommand(std::vector<Expression> const& _expressionsToEvaluate);
+ std::vector<std::string> parseValues(std::string::const_iterator _start, std::string::const_iterator _end);
+
+ z3::context m_context;
+ z3::solver m_solver;
+ std::map<std::string, z3::expr> m_constants;
+ std::map<std::string, z3::func_decl> m_functions;
+};
+
+}
+}
+}
diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp
index 74743737..6d0c0255 100644
--- a/libsolidity/inlineasm/AsmCodeGen.cpp
+++ b/libsolidity/inlineasm/AsmCodeGen.cpp
@@ -52,7 +52,7 @@ using namespace dev::solidity::assembly;
class EthAssemblyAdapter: public julia::AbstractAssembly
{
public:
- EthAssemblyAdapter(eth::Assembly& _assembly):
+ explicit EthAssemblyAdapter(eth::Assembly& _assembly):
m_assembly(_assembly)
{
}
@@ -127,7 +127,7 @@ public:
}
private:
- LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag) const
+ static LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag)
{
u256 id = _tag.data();
solAssert(id <= std::numeric_limits<LabelID>::max(), "Tag id too large.");
diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp
index 133f70b1..d84fe999 100644
--- a/libsolidity/inlineasm/AsmParser.cpp
+++ b/libsolidity/inlineasm/AsmParser.cpp
@@ -23,6 +23,9 @@
#include <libsolidity/inlineasm/AsmParser.h>
#include <libsolidity/parsing/Scanner.h>
#include <libsolidity/interface/ErrorReporter.h>
+
+#include <boost/algorithm/string.hpp>
+
#include <ctype.h>
#include <algorithm>
@@ -33,6 +36,7 @@ using namespace dev::solidity::assembly;
shared_ptr<assembly::Block> Parser::parse(std::shared_ptr<Scanner> const& _scanner)
{
+ m_recursionDepth = 0;
try
{
m_scanner = _scanner;
@@ -48,6 +52,7 @@ shared_ptr<assembly::Block> Parser::parse(std::shared_ptr<Scanner> const& _scann
assembly::Block Parser::parseBlock()
{
+ RecursionGuard recursionGuard(*this);
assembly::Block block = createWithLocation<Block>();
expectToken(Token::LBrace);
while (currentToken() != Token::RBrace)
@@ -59,6 +64,7 @@ assembly::Block Parser::parseBlock()
assembly::Statement Parser::parseStatement()
{
+ RecursionGuard recursionGuard(*this);
switch (currentToken())
{
case Token::Let:
@@ -155,6 +161,7 @@ assembly::Statement Parser::parseStatement()
assembly::Case Parser::parseCase()
{
+ RecursionGuard recursionGuard(*this);
assembly::Case _case = createWithLocation<assembly::Case>();
if (m_scanner->currentToken() == Token::Default)
m_scanner->next();
@@ -175,6 +182,7 @@ assembly::Case Parser::parseCase()
assembly::ForLoop Parser::parseForLoop()
{
+ RecursionGuard recursionGuard(*this);
ForLoop forLoop = createWithLocation<ForLoop>();
expectToken(Token::For);
forLoop.pre = parseBlock();
@@ -189,6 +197,7 @@ assembly::ForLoop Parser::parseForLoop()
assembly::Statement Parser::parseExpression()
{
+ RecursionGuard recursionGuard(*this);
Statement operation = parseElementaryOperation(true);
if (operation.type() == typeid(Instruction))
{
@@ -251,6 +260,7 @@ std::map<dev::solidity::Instruction, string> const& Parser::instructionNames()
assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
{
+ RecursionGuard recursionGuard(*this);
Statement ret;
switch (currentToken())
{
@@ -297,6 +307,8 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
kind = LiteralKind::String;
break;
case Token::Number:
+ if (!isValidNumberLiteral(currentLiteral()))
+ fatalParserError("Invalid number literal.");
kind = LiteralKind::Number;
break;
case Token::TrueLiteral:
@@ -337,6 +349,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
assembly::VariableDeclaration Parser::parseVariableDeclaration()
{
+ RecursionGuard recursionGuard(*this);
VariableDeclaration varDecl = createWithLocation<VariableDeclaration>();
expectToken(Token::Let);
while (true)
@@ -361,6 +374,7 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration()
assembly::FunctionDefinition Parser::parseFunctionDefinition()
{
+ RecursionGuard recursionGuard(*this);
FunctionDefinition funDef = createWithLocation<FunctionDefinition>();
expectToken(Token::Function);
funDef.name = expectAsmIdentifier();
@@ -392,6 +406,7 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition()
assembly::Statement Parser::parseCall(assembly::Statement&& _instruction)
{
+ RecursionGuard recursionGuard(*this);
if (_instruction.type() == typeid(Instruction))
{
solAssert(!m_julia, "Instructions are invalid in JULIA");
@@ -474,6 +489,7 @@ assembly::Statement Parser::parseCall(assembly::Statement&& _instruction)
TypedName Parser::parseTypedName()
{
+ RecursionGuard recursionGuard(*this);
TypedName typedName = createWithLocation<TypedName>();
typedName.name = expectAsmIdentifier();
if (m_julia)
@@ -501,3 +517,19 @@ string Parser::expectAsmIdentifier()
expectToken(Token::Identifier);
return name;
}
+
+bool Parser::isValidNumberLiteral(string const& _literal)
+{
+ try
+ {
+ u256(_literal);
+ }
+ catch (...)
+ {
+ return false;
+ }
+ if (boost::starts_with(_literal, "0x"))
+ return true;
+ else
+ return _literal.find_first_not_of("0123456789") == string::npos;
+}
diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h
index 45708afd..e46d1732 100644
--- a/libsolidity/inlineasm/AsmParser.h
+++ b/libsolidity/inlineasm/AsmParser.h
@@ -45,7 +45,7 @@ public:
protected:
/// Creates an inline assembly node with the given source location.
- template <class T> T createWithLocation(SourceLocation const& _loc = SourceLocation())
+ template <class T> T createWithLocation(SourceLocation const& _loc = SourceLocation()) const
{
T r;
r.location = _loc;
@@ -75,6 +75,8 @@ protected:
TypedName parseTypedName();
std::string expectAsmIdentifier();
+ static bool isValidNumberLiteral(std::string const& _literal);
+
private:
bool m_julia = false;
};
diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp
index 4f96a3e9..47ede91d 100644
--- a/libsolidity/inlineasm/AsmPrinter.cpp
+++ b/libsolidity/inlineasm/AsmPrinter.cpp
@@ -209,7 +209,7 @@ string AsmPrinter::operator()(Block const& _block)
return "{\n " + body + "\n}";
}
-string AsmPrinter::appendTypeName(std::string const& _type)
+string AsmPrinter::appendTypeName(std::string const& _type) const
{
if (m_julia)
return ":" + _type;
diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h
index f57dddc8..66520632 100644
--- a/libsolidity/inlineasm/AsmPrinter.h
+++ b/libsolidity/inlineasm/AsmPrinter.h
@@ -53,7 +53,7 @@ public:
std::string operator()(assembly::Block const& _block);
private:
- std::string appendTypeName(std::string const& _type);
+ std::string appendTypeName(std::string const& _type) const;
bool m_julia = false;
};
diff --git a/libsolidity/inlineasm/AsmScope.cpp b/libsolidity/inlineasm/AsmScope.cpp
index 315d5953..64d5bd9a 100644
--- a/libsolidity/inlineasm/AsmScope.cpp
+++ b/libsolidity/inlineasm/AsmScope.cpp
@@ -70,7 +70,7 @@ Scope::Identifier* Scope::lookup(string const& _name)
return nullptr;
}
-bool Scope::exists(string const& _name)
+bool Scope::exists(string const& _name) const
{
if (identifiers.count(_name))
return true;
diff --git a/libsolidity/inlineasm/AsmScope.h b/libsolidity/inlineasm/AsmScope.h
index cc240565..447d6490 100644
--- a/libsolidity/inlineasm/AsmScope.h
+++ b/libsolidity/inlineasm/AsmScope.h
@@ -107,7 +107,7 @@ struct Scope
}
/// @returns true if the name exists in this scope or in super scopes (also searches
/// across function and assembly boundaries).
- bool exists(std::string const& _name);
+ bool exists(std::string const& _name) const;
/// @returns the number of variables directly registered inside the scope.
size_t numberOfVariables() const;
diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp
index 12f958fc..3df9d1f8 100644
--- a/libsolidity/interface/ABI.cpp
+++ b/libsolidity/interface/ABI.cpp
@@ -19,7 +19,6 @@
*/
#include <libsolidity/interface/ABI.h>
-#include <boost/range/irange.hpp>
#include <libsolidity/ast/AST.h>
using namespace std;
@@ -36,8 +35,10 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
Json::Value method;
method["type"] = "function";
method["name"] = it.second->declaration().name();
- method["constant"] = it.second->isConstant();
+ // TODO: deprecate constant in a future release
+ method["constant"] = it.second->stateMutability() == StateMutability::Pure || it.second->stateMutability() == StateMutability::View;
method["payable"] = it.second->isPayable();
+ method["stateMutability"] = stateMutabilityToString(it.second->stateMutability());
method["inputs"] = formatTypeList(
externalFunctionType->parameterNames(),
externalFunctionType->parameterTypes(),
@@ -57,6 +58,7 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType();
solAssert(!!externalFunction, "");
method["payable"] = externalFunction->isPayable();
+ method["stateMutability"] = stateMutabilityToString(externalFunction->stateMutability());
method["inputs"] = formatTypeList(
externalFunction->parameterNames(),
externalFunction->parameterTypes(),
@@ -71,6 +73,7 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
Json::Value method;
method["type"] = "fallback";
method["payable"] = externalFunctionType->isPayable();
+ method["stateMutability"] = stateMutabilityToString(externalFunctionType->stateMutability());
abi.append(method);
}
for (auto const& it: _contractDef.interfaceEvents())
diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp
index 7c66a843..363f45dd 100644
--- a/libsolidity/interface/CompilerStack.cpp
+++ b/libsolidity/interface/CompilerStack.cpp
@@ -37,6 +37,7 @@
#include <libsolidity/analysis/PostTypeChecker.h>
#include <libsolidity/analysis/SyntaxChecker.h>
#include <libsolidity/codegen/Compiler.h>
+#include <libsolidity/formal/SMTChecker.h>
#include <libsolidity/interface/ABI.h>
#include <libsolidity/interface/Natspec.h>
#include <libsolidity/interface/GasEstimator.h>
@@ -49,8 +50,6 @@
#include <json/json.h>
#include <boost/algorithm/string.hpp>
-#include <boost/filesystem.hpp>
-
using namespace std;
using namespace dev;
@@ -240,6 +239,13 @@ bool CompilerStack::analyze()
if (noErrors)
{
+ SMTChecker smtChecker(m_errorReporter, m_smtQuery);
+ for (Source const* source: m_sourceOrder)
+ smtChecker.analyze(*source->ast);
+ }
+
+ if (noErrors)
+ {
m_stackState = AnalysisSuccessful;
return true;
}
@@ -406,39 +412,42 @@ Json::Value const& CompilerStack::contractABI(Contract const& _contract) const
return *_contract.abi;
}
-Json::Value const& CompilerStack::natspec(string const& _contractName, DocumentationType _type) const
+Json::Value const& CompilerStack::natspecUser(string const& _contractName) const
{
- return natspec(contract(_contractName), _type);
+ return natspecUser(contract(_contractName));
}
-Json::Value const& CompilerStack::natspec(Contract const& _contract, DocumentationType _type) const
+Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const
{
if (m_stackState < AnalysisSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
solAssert(_contract.contract, "");
- std::unique_ptr<Json::Value const>* doc;
- // checks wheather we already have the documentation
- switch (_type)
- {
- case DocumentationType::NatspecUser:
- doc = &_contract.userDocumentation;
- // caches the result
- if (!*doc)
- doc->reset(new Json::Value(Natspec::userDocumentation(*_contract.contract)));
- break;
- case DocumentationType::NatspecDev:
- doc = &_contract.devDocumentation;
- // caches the result
- if (!*doc)
- doc->reset(new Json::Value(Natspec::devDocumentation(*_contract.contract)));
- break;
- default:
- solAssert(false, "Illegal documentation type.");
- }
+ // caches the result
+ if (!_contract.userDocumentation)
+ _contract.userDocumentation.reset(new Json::Value(Natspec::userDocumentation(*_contract.contract)));
+
+ return *_contract.userDocumentation;
+}
- return *(*doc);
+Json::Value const& CompilerStack::natspecDev(string const& _contractName) const
+{
+ return natspecDev(contract(_contractName));
+}
+
+Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const
+{
+ if (m_stackState < AnalysisSuccessful)
+ BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
+
+ solAssert(_contract.contract, "");
+
+ // caches the result
+ if (!_contract.devDocumentation)
+ _contract.devDocumentation.reset(new Json::Value(Natspec::devDocumentation(*_contract.contract)));
+
+ return *_contract.devDocumentation;
}
Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const
@@ -526,17 +535,17 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string
if (m_sources.count(importPath) || newSources.count(importPath))
continue;
- ReadFile::Result result{false, string("File not supplied initially.")};
+ ReadCallback::Result result{false, string("File not supplied initially.")};
if (m_readFile)
result = m_readFile(importPath);
if (result.success)
- newSources[importPath] = result.contentsOrErrorMessage;
+ newSources[importPath] = result.responseOrErrorMessage;
else
{
m_errorReporter.parserError(
import->location(),
- string("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMessage)
+ string("Source \"" + importPath + "\" not found: " + result.responseOrErrorMessage)
);
continue;
}
@@ -632,6 +641,17 @@ string CompilerStack::absolutePath(string const& _path, string const& _reference
return result.generic_string();
}
+namespace
+{
+bool onlySafeExperimentalFeaturesActivated(set<ExperimentalFeature> const& features)
+{
+ for (auto const feature: features)
+ if (!ExperimentalFeatureOnlyAnalysis.count(feature))
+ return false;
+ return true;
+}
+}
+
void CompilerStack::compileContract(
ContractDefinition const& _contract,
map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts
@@ -649,10 +669,23 @@ void CompilerStack::compileContract(
shared_ptr<Compiler> compiler = make_shared<Compiler>(m_optimize, m_optimizeRuns);
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
string metadata = createMetadata(compiledContract);
- bytes cborEncodedMetadata =
- // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata)}
- bytes{0xa1, 0x65, 'b', 'z', 'z', 'r', '0', 0x58, 0x20} +
- dev::swarmHash(metadata).asBytes();
+ bytes cborEncodedHash =
+ // CBOR-encoding of the key "bzzr0"
+ bytes{0x65, 'b', 'z', 'z', 'r', '0'}+
+ // CBOR-encoding of the hash
+ bytes{0x58, 0x20} + dev::swarmHash(metadata).asBytes();
+ bytes cborEncodedMetadata;
+ if (onlySafeExperimentalFeaturesActivated(_contract.sourceUnit().annotation().experimentalFeatures))
+ cborEncodedMetadata =
+ // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata)}
+ bytes{0xa1} +
+ cborEncodedHash;
+ else
+ cborEncodedMetadata =
+ // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata), "experimental": true}
+ bytes{0xa2} +
+ cborEncodedHash +
+ bytes{0x6c, 'e', 'x', 'p', 'e', 'r', 'i', 'm', 'e', 'n', 't', 'a', 'l', 0xf5};
solAssert(cborEncodedMetadata.size() <= 0xffff, "Metadata too large");
// 16-bit big endian length
cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2);
@@ -795,8 +828,8 @@ string CompilerStack::createMetadata(Contract const& _contract) const
meta["settings"]["libraries"][library.first] = "0x" + toHex(library.second.asBytes());
meta["output"]["abi"] = contractABI(_contract);
- meta["output"]["userdoc"] = natspec(_contract, DocumentationType::NatspecUser);
- meta["output"]["devdoc"] = natspec(_contract, DocumentationType::NatspecDev);
+ meta["output"]["userdoc"] = natspecUser(_contract);
+ meta["output"]["devdoc"] = natspecDev(_contract);
return jsonCompactPrint(meta);
}
diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h
index d287f224..361b8a45 100644
--- a/libsolidity/interface/CompilerStack.h
+++ b/libsolidity/interface/CompilerStack.h
@@ -63,12 +63,6 @@ class Natspec;
class Error;
class DeclarationContainer;
-enum class DocumentationType: uint8_t
-{
- NatspecUser = 1,
- NatspecDev
-};
-
/**
* Easy to use and self-contained Solidity compiler with as few header dependencies as possible.
* It holds state and can be used to either step through the compilation stages (and abort e.g.
@@ -88,13 +82,13 @@ public:
/// Creates a new compiler stack.
/// @param _readFile callback to used to read files for import statements. Must return
/// and must not emit exceptions.
- explicit CompilerStack(ReadFile::Callback const& _readFile = ReadFile::Callback()):
+ explicit CompilerStack(ReadCallback::Callback const& _readFile = ReadCallback::Callback()):
m_readFile(_readFile),
m_errorList(),
m_errorReporter(m_errorList) {}
/// @returns the list of errors that occured during parsing and type checking.
- ErrorList const& errors() { return m_errorReporter.errors(); }
+ ErrorList const& errors() const { return m_errorReporter.errors(); }
/// @returns the current state.
State state() const { return m_stackState; }
@@ -203,11 +197,13 @@ public:
/// Prerequisite: Successful call to parse or compile.
Json::Value const& contractABI(std::string const& _contractName = "") const;
- /// @returns a JSON representing the contract's documentation.
+ /// @returns a JSON representing the contract's user documentation.
+ /// Prerequisite: Successful call to parse or compile.
+ Json::Value const& natspecUser(std::string const& _contractName) const;
+
+ /// @returns a JSON representing the contract's developer documentation.
/// Prerequisite: Successful call to parse or compile.
- /// @param type The type of the documentation to get.
- /// Can be one of 4 types defined at @c DocumentationType
- Json::Value const& natspec(std::string const& _contractName, DocumentationType _type) const;
+ Json::Value const& natspecDev(std::string const& _contractName) const;
/// @returns a JSON representing a map of method identifiers (hashes) to function names.
Json::Value methodIdentifiers(std::string const& _contractName) const;
@@ -274,7 +270,8 @@ private:
std::string createMetadata(Contract const& _contract) const;
std::string computeSourceMapping(eth::AssemblyItems const& _items) const;
Json::Value const& contractABI(Contract const&) const;
- Json::Value const& natspec(Contract const&, DocumentationType _type) const;
+ Json::Value const& natspecUser(Contract const&) const;
+ Json::Value const& natspecDev(Contract const&) const;
/// @returns the offset of the entry point of the given function into the list of assembly items
/// or zero if it is not found or does not exist.
@@ -290,7 +287,8 @@ private:
std::string target;
};
- ReadFile::Callback m_readFile;
+ ReadCallback::Callback m_readFile;
+ ReadCallback::Callback m_smtQuery;
bool m_optimize = false;
unsigned m_optimizeRuns = 200;
std::map<std::string, h160> m_libraries;
diff --git a/libsolidity/interface/ErrorReporter.h b/libsolidity/interface/ErrorReporter.h
index 42b0c8b6..241d6b43 100644
--- a/libsolidity/interface/ErrorReporter.h
+++ b/libsolidity/interface/ErrorReporter.h
@@ -36,7 +36,7 @@ class ErrorReporter
{
public:
- ErrorReporter(ErrorList& _errors):
+ explicit ErrorReporter(ErrorList& _errors):
m_errorList(_errors) { }
ErrorReporter& operator=(ErrorReporter const& _errorReporter);
@@ -75,8 +75,8 @@ public:
void typeError(
SourceLocation const& _location,
- SecondarySourceLocation const& _secondaryLocation,
- std::string const& _description
+ SecondarySourceLocation const& _secondaryLocation = SecondarySourceLocation(),
+ std::string const& _description = std::string()
);
void typeError(SourceLocation const& _location, std::string const& _description);
diff --git a/libsolidity/interface/ReadFile.h b/libsolidity/interface/ReadFile.h
index 2e8a6bd8..7068629d 100644
--- a/libsolidity/interface/ReadFile.h
+++ b/libsolidity/interface/ReadFile.h
@@ -27,17 +27,17 @@ namespace dev
namespace solidity
{
-class ReadFile: boost::noncopyable
+class ReadCallback: boost::noncopyable
{
public:
- /// File reading result.
+ /// File reading or generic query result.
struct Result
{
bool success;
- std::string contentsOrErrorMessage;
+ std::string responseOrErrorMessage;
};
- /// File reading callback.
+ /// File reading or generic query callback.
using Callback = std::function<Result(std::string const&)>;
};
diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp
index dd135ce5..be823743 100644
--- a/libsolidity/interface/StandardCompiler.cpp
+++ b/libsolidity/interface/StandardCompiler.cpp
@@ -203,10 +203,10 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
for (auto const& url: sources[sourceName]["urls"])
{
- ReadFile::Result result = m_readFile(url.asString());
+ ReadCallback::Result result = m_readFile(url.asString());
if (result.success)
{
- if (!hash.empty() && !hashMatchesContent(hash, result.contentsOrErrorMessage))
+ if (!hash.empty() && !hashMatchesContent(hash, result.responseOrErrorMessage))
errors.append(formatError(
false,
"IOError",
@@ -215,13 +215,13 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
));
else
{
- m_compilerStack.addSource(sourceName, result.contentsOrErrorMessage);
+ m_compilerStack.addSource(sourceName, result.responseOrErrorMessage);
found = true;
break;
}
}
else
- failures.push_back("Cannot import url (\"" + url.asString() + "\"): " + result.contentsOrErrorMessage);
+ failures.push_back("Cannot import url (\"" + url.asString() + "\"): " + result.responseOrErrorMessage);
}
for (auto const& failure: failures)
@@ -394,8 +394,8 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
Json::Value contractData(Json::objectValue);
contractData["abi"] = m_compilerStack.contractABI(contractName);
contractData["metadata"] = m_compilerStack.metadata(contractName);
- contractData["userdoc"] = m_compilerStack.natspec(contractName, DocumentationType::NatspecUser);
- contractData["devdoc"] = m_compilerStack.natspec(contractName, DocumentationType::NatspecDev);
+ contractData["userdoc"] = m_compilerStack.natspecUser(contractName);
+ contractData["devdoc"] = m_compilerStack.natspecDev(contractName);
// EVM
Json::Value evmData(Json::objectValue);
diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h
index dfaf88cd..11a0b4c2 100644
--- a/libsolidity/interface/StandardCompiler.h
+++ b/libsolidity/interface/StandardCompiler.h
@@ -40,7 +40,7 @@ public:
/// Creates a new StandardCompiler.
/// @param _readFile callback to used to read files for import statements. Must return
/// and must not emit exceptions.
- StandardCompiler(ReadFile::Callback const& _readFile = ReadFile::Callback())
+ explicit StandardCompiler(ReadCallback::Callback const& _readFile = ReadCallback::Callback())
: m_compilerStack(_readFile), m_readFile(_readFile)
{
}
@@ -56,7 +56,7 @@ private:
Json::Value compileInternal(Json::Value const& _input);
CompilerStack m_compilerStack;
- ReadFile::Callback m_readFile;
+ ReadCallback::Callback m_readFile;
};
}
diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp
index b98991f3..ddfdb667 100644
--- a/libsolidity/parsing/Parser.cpp
+++ b/libsolidity/parsing/Parser.cpp
@@ -40,7 +40,7 @@ namespace solidity
class Parser::ASTNodeFactory
{
public:
- ASTNodeFactory(Parser const& _parser):
+ explicit ASTNodeFactory(Parser const& _parser):
m_parser(_parser), m_location(_parser.position(), -1, _parser.sourceName()) {}
ASTNodeFactory(Parser const& _parser, ASTPointer<ASTNode> const& _childNode):
m_parser(_parser), m_location(_childNode->location()) {}
@@ -68,6 +68,7 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
{
try
{
+ m_recursionDepth = 0;
m_scanner = _scanner;
ASTNodeFactory nodeFactory(*this);
vector<ASTPointer<ASTNode>> nodes;
@@ -90,6 +91,7 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
fatalParserError(string("Expected pragma, import directive or contract/interface/library definition."));
}
}
+ solAssert(m_recursionDepth == 0, "");
return nodeFactory.createNode<SourceUnit>(nodes);
}
catch (FatalError const&)
@@ -102,6 +104,7 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner)
ASTPointer<PragmaDirective> Parser::parsePragmaDirective()
{
+ RecursionGuard recursionGuard(*this);
// pragma anything* ;
// Currently supported:
// pragma solidity ^0.4.0 || ^0.3.0;
@@ -132,6 +135,7 @@ ASTPointer<PragmaDirective> Parser::parsePragmaDirective()
ASTPointer<ImportDirective> Parser::parseImportDirective()
{
+ RecursionGuard recursionGuard(*this);
// import "abc" [as x];
// import * as x from "abc";
// import {a as b, c} from "abc";
@@ -212,6 +216,7 @@ ContractDefinition::ContractKind Parser::tokenToContractKind(Token::Value _token
ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _expectedKind)
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
ASTPointer<ASTString> docString;
if (m_scanner->currentCommentLiteral() != "")
@@ -275,6 +280,7 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _exp
ASTPointer<InheritanceSpecifier> Parser::parseInheritanceSpecifier()
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
ASTPointer<UserDefinedTypeName> name(parseUserDefinedTypeName());
vector<ASTPointer<Expression>> arguments;
@@ -307,8 +313,25 @@ Declaration::Visibility Parser::parseVisibilitySpecifier(Token::Value _token)
return visibility;
}
+StateMutability Parser::parseStateMutability(Token::Value _token)
+{
+ StateMutability stateMutability(StateMutability::NonPayable);
+ if (_token == Token::Payable)
+ stateMutability = StateMutability::Payable;
+ // FIXME: constant should be removed at the next breaking release
+ else if (_token == Token::View || _token == Token::Constant)
+ stateMutability = StateMutability::View;
+ else if (_token == Token::Pure)
+ stateMutability = StateMutability::Pure;
+ else
+ solAssert(false, "Invalid state mutability specifier.");
+ m_scanner->next();
+ return stateMutability;
+}
+
Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers)
{
+ RecursionGuard recursionGuard(*this);
FunctionHeaderParserResult result;
expectToken(Token::Function);
if (_forceEmptyName || m_scanner->currentToken() == Token::LParen)
@@ -321,23 +344,7 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN
while (true)
{
Token::Value token = m_scanner->currentToken();
- if (token == Token::Const)
- {
- if (result.isDeclaredConst)
- parserError(string("Multiple \"constant\" specifiers."));
-
- result.isDeclaredConst = true;
- m_scanner->next();
- }
- else if (m_scanner->currentToken() == Token::Payable)
- {
- if (result.isPayable)
- parserError(string("Multiple \"payable\" specifiers."));
-
- result.isPayable = true;
- m_scanner->next();
- }
- else if (_allowModifiers && token == Token::Identifier)
+ if (_allowModifiers && token == Token::Identifier)
{
// This can either be a modifier (function declaration) or the name of the
// variable (function type name plus variable).
@@ -354,12 +361,30 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN
{
if (result.visibility != Declaration::Visibility::Default)
{
- parserError(string("Multiple visibility specifiers."));
+ parserError(string(
+ "Visibility already specified as \"" +
+ Declaration::visibilityToString(result.visibility) +
+ "\"."
+ ));
m_scanner->next();
}
else
result.visibility = parseVisibilitySpecifier(token);
}
+ else if (Token::isStateMutabilitySpecifier(token))
+ {
+ if (result.stateMutability != StateMutability::NonPayable)
+ {
+ parserError(string(
+ "State mutability already specified as \"" +
+ stateMutabilityToString(result.stateMutability) +
+ "\"."
+ ));
+ m_scanner->next();
+ }
+ else
+ result.stateMutability = parseStateMutability(token);
+ }
else
break;
}
@@ -376,6 +401,7 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN
ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName)
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
ASTPointer<ASTString> docstring;
if (m_scanner->currentCommentLiteral() != "")
@@ -404,13 +430,12 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A
return nodeFactory.createNode<FunctionDefinition>(
header.name,
header.visibility,
+ header.stateMutability,
c_isConstructor,
docstring,
header.parameters,
- header.isDeclaredConst,
header.modifiers,
header.returnParameters,
- header.isPayable,
block
);
}
@@ -421,8 +446,7 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A
header.parameters,
header.returnParameters,
header.visibility,
- header.isDeclaredConst,
- header.isPayable
+ header.stateMutability
);
type = parseTypeNameSuffix(type, nodeFactory);
VarDeclParserOptions options;
@@ -436,6 +460,7 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A
ASTPointer<StructDefinition> Parser::parseStructDefinition()
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
expectToken(Token::Struct);
ASTPointer<ASTString> name = expectIdentifierToken();
@@ -453,6 +478,7 @@ ASTPointer<StructDefinition> Parser::parseStructDefinition()
ASTPointer<EnumValue> Parser::parseEnumValue()
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
nodeFactory.markEndPosition();
return nodeFactory.createNode<EnumValue>(expectIdentifierToken());
@@ -460,6 +486,7 @@ ASTPointer<EnumValue> Parser::parseEnumValue()
ASTPointer<EnumDefinition> Parser::parseEnumDefinition()
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
expectToken(Token::Enum);
ASTPointer<ASTString> name = expectIdentifierToken();
@@ -488,6 +515,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
ASTPointer<TypeName> const& _lookAheadArrayType
)
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory = _lookAheadArrayType ?
ASTNodeFactory(*this, _lookAheadArrayType) : ASTNodeFactory(*this);
ASTPointer<TypeName> type;
@@ -512,7 +540,11 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
{
if (visibility != Declaration::Visibility::Default)
{
- parserError(string("Visibility already specified."));
+ parserError(string(
+ "Visibility already specified as \"" +
+ Declaration::visibilityToString(visibility) +
+ "\"."
+ ));
m_scanner->next();
}
else
@@ -522,7 +554,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
{
if (_options.allowIndexed && token == Token::Indexed)
isIndexed = true;
- else if (token == Token::Const)
+ else if (token == Token::Constant)
isDeclaredConst = true;
else if (_options.allowLocationSpecifier && Token::isLocationSpecifier(token))
{
@@ -576,6 +608,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
ASTPointer<ModifierDefinition> Parser::parseModifierDefinition()
{
+ RecursionGuard recursionGuard(*this);
ScopeGuard resetModifierFlag([this]() { m_insideModifier = false; });
m_insideModifier = true;
@@ -603,6 +636,7 @@ ASTPointer<ModifierDefinition> Parser::parseModifierDefinition()
ASTPointer<EventDefinition> Parser::parseEventDefinition()
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
ASTPointer<ASTString> docstring;
if (m_scanner->currentCommentLiteral() != "")
@@ -632,6 +666,7 @@ ASTPointer<EventDefinition> Parser::parseEventDefinition()
ASTPointer<UsingForDirective> Parser::parseUsingDirective()
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
expectToken(Token::Using);
@@ -649,6 +684,7 @@ ASTPointer<UsingForDirective> Parser::parseUsingDirective()
ASTPointer<ModifierInvocation> Parser::parseModifierInvocation()
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
ASTPointer<Identifier> name(parseIdentifier());
vector<ASTPointer<Expression>> arguments;
@@ -666,6 +702,7 @@ ASTPointer<ModifierInvocation> Parser::parseModifierInvocation()
ASTPointer<Identifier> Parser::parseIdentifier()
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
nodeFactory.markEndPosition();
return nodeFactory.createNode<Identifier>(expectIdentifierToken());
@@ -673,6 +710,7 @@ ASTPointer<Identifier> Parser::parseIdentifier()
ASTPointer<UserDefinedTypeName> Parser::parseUserDefinedTypeName()
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
nodeFactory.markEndPosition();
vector<ASTString> identifierPath{*expectIdentifierToken()};
@@ -687,6 +725,7 @@ ASTPointer<UserDefinedTypeName> Parser::parseUserDefinedTypeName()
ASTPointer<TypeName> Parser::parseTypeNameSuffix(ASTPointer<TypeName> type, ASTNodeFactory& nodeFactory)
{
+ RecursionGuard recursionGuard(*this);
while (m_scanner->currentToken() == Token::LBrack)
{
m_scanner->next();
@@ -702,6 +741,7 @@ ASTPointer<TypeName> Parser::parseTypeNameSuffix(ASTPointer<TypeName> type, ASTN
ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar)
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
ASTPointer<TypeName> type;
Token::Value token = m_scanner->currentToken();
@@ -737,19 +777,20 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar)
ASTPointer<FunctionTypeName> Parser::parseFunctionType()
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
FunctionHeaderParserResult header = parseFunctionHeader(true, false);
return nodeFactory.createNode<FunctionTypeName>(
header.parameters,
header.returnParameters,
header.visibility,
- header.isDeclaredConst,
- header.isPayable
+ header.stateMutability
);
}
ASTPointer<Mapping> Parser::parseMapping()
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
expectToken(Token::Mapping);
expectToken(Token::LParen);
@@ -776,6 +817,7 @@ ASTPointer<ParameterList> Parser::parseParameterList(
bool _allowEmpty
)
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
vector<ASTPointer<VariableDeclaration>> parameters;
VarDeclParserOptions options(_options);
@@ -797,6 +839,7 @@ ASTPointer<ParameterList> Parser::parseParameterList(
ASTPointer<Block> Parser::parseBlock(ASTPointer<ASTString> const& _docString)
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
expectToken(Token::LBrace);
vector<ASTPointer<Statement>> statements;
@@ -809,6 +852,7 @@ ASTPointer<Block> Parser::parseBlock(ASTPointer<ASTString> const& _docString)
ASTPointer<Statement> Parser::parseStatement()
{
+ RecursionGuard recursionGuard(*this);
ASTPointer<ASTString> docString;
if (m_scanner->currentCommentLiteral() != "")
docString = make_shared<ASTString>(m_scanner->currentCommentLiteral());
@@ -871,6 +915,7 @@ ASTPointer<Statement> Parser::parseStatement()
ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> const& _docString)
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
expectToken(Token::Assembly);
if (m_scanner->currentToken() == Token::StringLiteral)
@@ -888,6 +933,7 @@ ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> con
ASTPointer<IfStatement> Parser::parseIfStatement(ASTPointer<ASTString> const& _docString)
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
expectToken(Token::If);
expectToken(Token::LParen);
@@ -908,6 +954,7 @@ ASTPointer<IfStatement> Parser::parseIfStatement(ASTPointer<ASTString> const& _d
ASTPointer<WhileStatement> Parser::parseWhileStatement(ASTPointer<ASTString> const& _docString)
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
expectToken(Token::While);
expectToken(Token::LParen);
@@ -920,6 +967,7 @@ ASTPointer<WhileStatement> Parser::parseWhileStatement(ASTPointer<ASTString> con
ASTPointer<WhileStatement> Parser::parseDoWhileStatement(ASTPointer<ASTString> const& _docString)
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
expectToken(Token::Do);
ASTPointer<Statement> body = parseStatement();
@@ -935,6 +983,7 @@ ASTPointer<WhileStatement> Parser::parseDoWhileStatement(ASTPointer<ASTString> c
ASTPointer<ForStatement> Parser::parseForStatement(ASTPointer<ASTString> const& _docString)
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
ASTPointer<Statement> initExpression;
ASTPointer<Expression> conditionExpression;
@@ -968,6 +1017,7 @@ ASTPointer<ForStatement> Parser::parseForStatement(ASTPointer<ASTString> const&
ASTPointer<Statement> Parser::parseSimpleStatement(ASTPointer<ASTString> const& _docString)
{
+ RecursionGuard recursionGuard(*this);
// These two cases are very hard to distinguish:
// x[7 * 20 + 3] a; - x[7 * 20 + 3] = 9;
// In the first case, x is a type name, in the second it is the name of a variable.
@@ -1030,6 +1080,7 @@ ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStateme
ASTPointer<TypeName> const& _lookAheadArrayType
)
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
if (_lookAheadArrayType)
nodeFactory.setLocation(_lookAheadArrayType->location());
@@ -1093,6 +1144,7 @@ ASTPointer<ExpressionStatement> Parser::parseExpressionStatement(
ASTPointer<Expression> const& _lookAheadIndexAccessStructure
)
{
+ RecursionGuard recursionGuard(*this);
ASTPointer<Expression> expression = parseExpression(_lookAheadIndexAccessStructure);
return ASTNodeFactory(*this, expression).createNode<ExpressionStatement>(_docString, expression);
}
@@ -1101,6 +1153,7 @@ ASTPointer<Expression> Parser::parseExpression(
ASTPointer<Expression> const& _lookAheadIndexAccessStructure
)
{
+ RecursionGuard recursionGuard(*this);
ASTPointer<Expression> expression = parseBinaryExpression(4, _lookAheadIndexAccessStructure);
if (Token::isAssignmentOp(m_scanner->currentToken()))
{
@@ -1129,6 +1182,7 @@ ASTPointer<Expression> Parser::parseBinaryExpression(
ASTPointer<Expression> const& _lookAheadIndexAccessStructure
)
{
+ RecursionGuard recursionGuard(*this);
ASTPointer<Expression> expression = parseUnaryExpression(_lookAheadIndexAccessStructure);
ASTNodeFactory nodeFactory(*this, expression);
int precedence = Token::precedence(m_scanner->currentToken());
@@ -1148,6 +1202,7 @@ ASTPointer<Expression> Parser::parseUnaryExpression(
ASTPointer<Expression> const& _lookAheadIndexAccessStructure
)
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory = _lookAheadIndexAccessStructure ?
ASTNodeFactory(*this, _lookAheadIndexAccessStructure) : ASTNodeFactory(*this);
Token::Value token = m_scanner->currentToken();
@@ -1176,6 +1231,7 @@ ASTPointer<Expression> Parser::parseLeftHandSideExpression(
ASTPointer<Expression> const& _lookAheadIndexAccessStructure
)
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory = _lookAheadIndexAccessStructure ?
ASTNodeFactory(*this, _lookAheadIndexAccessStructure) : ASTNodeFactory(*this);
@@ -1233,6 +1289,7 @@ ASTPointer<Expression> Parser::parseLeftHandSideExpression(
ASTPointer<Expression> Parser::parsePrimaryExpression()
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
Token::Value token = m_scanner->currentToken();
ASTPointer<Expression> expression;
@@ -1292,10 +1349,11 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
parserError("Expected expression (inline array elements cannot be omitted).");
else
components.push_back(ASTPointer<Expression>());
+
if (m_scanner->currentToken() == oppositeToken)
break;
- else if (m_scanner->currentToken() == Token::Comma)
- m_scanner->next();
+
+ expectToken(Token::Comma);
}
nodeFactory.markEndPosition();
expectToken(oppositeToken);
@@ -1322,6 +1380,7 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
vector<ASTPointer<Expression>> Parser::parseFunctionCallListArguments()
{
+ RecursionGuard recursionGuard(*this);
vector<ASTPointer<Expression>> arguments;
if (m_scanner->currentToken() != Token::RParen)
{
@@ -1337,6 +1396,7 @@ vector<ASTPointer<Expression>> Parser::parseFunctionCallListArguments()
pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::parseFunctionCallArguments()
{
+ RecursionGuard recursionGuard(*this);
pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> ret;
Token::Value token = m_scanner->currentToken();
if (token == Token::LBrace)
@@ -1403,6 +1463,7 @@ ASTPointer<TypeName> Parser::typeNameIndexAccessStructure(
)
{
solAssert(!_path.empty(), "");
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
SourceLocation location = _path.front()->location();
location.end = _path.back()->location().end;
@@ -1435,6 +1496,7 @@ ASTPointer<Expression> Parser::expressionFromIndexAccessStructure(
)
{
solAssert(!_path.empty(), "");
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this, _path.front());
ASTPointer<Expression> expression(_path.front());
for (size_t i = 1; i < _path.size(); ++i)
@@ -1458,6 +1520,7 @@ ASTPointer<Expression> Parser::expressionFromIndexAccessStructure(
ASTPointer<ParameterList> Parser::createEmptyParameterList()
{
+ RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
nodeFactory.setLocationEmpty();
return nodeFactory.createNode<ParameterList>(vector<ASTPointer<VariableDeclaration>>());
diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h
index 19631c58..cfdfea7e 100644
--- a/libsolidity/parsing/Parser.h
+++ b/libsolidity/parsing/Parser.h
@@ -35,7 +35,7 @@ class Scanner;
class Parser: public ParserBase
{
public:
- Parser(ErrorReporter& _errorReporter): ParserBase(_errorReporter) {}
+ explicit Parser(ErrorReporter& _errorReporter): ParserBase(_errorReporter) {}
ASTPointer<SourceUnit> parse(std::shared_ptr<Scanner> const& _scanner);
@@ -60,8 +60,7 @@ private:
ASTPointer<ParameterList> parameters;
ASTPointer<ParameterList> returnParameters;
Declaration::Visibility visibility = Declaration::Visibility::Default;
- bool isDeclaredConst = false;
- bool isPayable = false;
+ StateMutability stateMutability = StateMutability::NonPayable;
std::vector<ASTPointer<ModifierInvocation>> modifiers;
};
@@ -73,6 +72,7 @@ private:
ASTPointer<ContractDefinition> parseContractDefinition(Token::Value _expectedKind);
ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier();
Declaration::Visibility parseVisibilitySpecifier(Token::Value _token);
+ StateMutability parseStateMutability(Token::Value _token);
FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers);
ASTPointer<ASTNode> parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName);
ASTPointer<FunctionDefinition> parseFunctionDefinition(ASTString const* _contractName);
diff --git a/libsolidity/parsing/ParserBase.cpp b/libsolidity/parsing/ParserBase.cpp
index 5657c2c0..fe95b0fe 100644
--- a/libsolidity/parsing/ParserBase.cpp
+++ b/libsolidity/parsing/ParserBase.cpp
@@ -101,6 +101,19 @@ void ParserBase::expectToken(Token::Value _value)
m_scanner->next();
}
+void ParserBase::increaseRecursionDepth()
+{
+ m_recursionDepth++;
+ if (m_recursionDepth >= 3000)
+ fatalParserError("Maximum recursion depth reached during parsing.");
+}
+
+void ParserBase::decreaseRecursionDepth()
+{
+ solAssert(m_recursionDepth > 0, "");
+ m_recursionDepth--;
+}
+
void ParserBase::parserError(string const& _description)
{
m_errorReporter.parserError(SourceLocation(position(), position(), sourceName()), _description);
diff --git a/libsolidity/parsing/ParserBase.h b/libsolidity/parsing/ParserBase.h
index 5b03ab5e..fd0de0d1 100644
--- a/libsolidity/parsing/ParserBase.h
+++ b/libsolidity/parsing/ParserBase.h
@@ -36,11 +36,25 @@ class Scanner;
class ParserBase
{
public:
- ParserBase(ErrorReporter& errorReporter): m_errorReporter(errorReporter) {}
+ explicit ParserBase(ErrorReporter& errorReporter): m_errorReporter(errorReporter) {}
std::shared_ptr<std::string const> const& sourceName() const;
protected:
+ /// Utility class that creates an error and throws an exception if the
+ /// recursion depth is too deep.
+ class RecursionGuard
+ {
+ public:
+ explicit RecursionGuard(ParserBase& _parser): m_parser(_parser)
+ {
+ m_parser.increaseRecursionDepth();
+ }
+ ~RecursionGuard() { m_parser.decreaseRecursionDepth(); }
+ private:
+ ParserBase& m_parser;
+ };
+
/// Start position of the current token
int position() const;
/// End position of the current token
@@ -56,6 +70,10 @@ protected:
Token::Value advance();
///@}
+ /// Increases the recursion depth and throws an exception if it is too deep.
+ void increaseRecursionDepth();
+ void decreaseRecursionDepth();
+
/// Creates a @ref ParserError and annotates it with the current position and the
/// given @a _description.
void parserError(std::string const& _description);
@@ -67,6 +85,8 @@ protected:
std::shared_ptr<Scanner> m_scanner;
/// The reference to the list of errors and warning to add errors/warnings during parsing
ErrorReporter& m_errorReporter;
+ /// Current recursion depth during parsing.
+ size_t m_recursionDepth = 0;
};
}
diff --git a/libsolidity/parsing/Scanner.h b/libsolidity/parsing/Scanner.h
index d6b48c6f..0adaa6fd 100644
--- a/libsolidity/parsing/Scanner.h
+++ b/libsolidity/parsing/Scanner.h
@@ -75,7 +75,7 @@ public:
int position() const { return m_position; }
bool isPastEndOfInput(size_t _charsForward = 0) const { return (m_position + _charsForward) >= m_source.size(); }
char get(size_t _charsForward = 0) const { return m_source[m_position + _charsForward]; }
- char advanceAndGet(size_t _chars=1);
+ char advanceAndGet(size_t _chars = 1);
char rollback(size_t _amount);
void reset() { m_position = 0; }
@@ -118,11 +118,11 @@ public:
///@name Information about the current token
/// @returns the current token
- Token::Value currentToken()
+ Token::Value currentToken() const
{
return m_currentToken.token;
}
- ElementaryTypeNameToken currentElementaryTypeNameToken()
+ ElementaryTypeNameToken currentElementaryTypeNameToken() const
{
unsigned firstSize;
unsigned secondSize;
@@ -219,8 +219,8 @@ private:
bool scanEscape();
/// Return the current source position.
- int sourcePos() { return m_source.position(); }
- bool isSourcePastEndOfInput() { return m_source.isPastEndOfInput(); }
+ int sourcePos() const { return m_source.position(); }
+ bool isSourcePastEndOfInput() const { return m_source.isPastEndOfInput(); }
TokenDesc m_skippedComment; // desc for current skipped comment
TokenDesc m_nextSkippedComment; // desc for next skiped comment
diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h
index d412b3f0..805fbf5d 100644
--- a/libsolidity/parsing/Token.h
+++ b/libsolidity/parsing/Token.h
@@ -143,7 +143,7 @@ namespace solidity
K(As, "as", 0) \
K(Assembly, "assembly", 0) \
K(Break, "break", 0) \
- K(Const, "constant", 0) \
+ K(Constant, "constant", 0) \
K(Continue, "continue", 0) \
K(Contract, "contract", 0) \
K(Do, "do", 0) \
@@ -169,6 +169,7 @@ namespace solidity
K(Public, "public", 0) \
K(Pragma, "pragma", 0) \
K(Private, "private", 0) \
+ K(Pure, "pure", 0) \
K(Return, "return", 0) \
K(Returns, "returns", 0) \
K(Storage, "storage", 0) \
@@ -176,6 +177,7 @@ namespace solidity
K(Throw, "throw", 0) \
K(Using, "using", 0) \
K(Var, "var", 0) \
+ K(View, "view", 0) \
K(While, "while", 0) \
\
/* Ether subdenominations */ \
@@ -229,14 +231,12 @@ namespace solidity
K(Match, "match", 0) \
K(NullLiteral, "null", 0) \
K(Of, "of", 0) \
- K(Pure, "pure", 0) \
K(Relocatable, "relocatable", 0) \
K(Static, "static", 0) \
K(Switch, "switch", 0) \
K(Try, "try", 0) \
K(Type, "type", 0) \
K(TypeOf, "typeof", 0) \
- K(View, "view", 0) \
/* Illegal token - not able to scan. */ \
T(Illegal, "ILLEGAL", 0) \
\
@@ -290,6 +290,7 @@ public:
static bool isVisibilitySpecifier(Value op) { return isVariableVisibilitySpecifier(op) || op == External; }
static bool isVariableVisibilitySpecifier(Value op) { return op == Public || op == Private || op == Internal; }
static bool isLocationSpecifier(Value op) { return op == Memory || op == Storage; }
+ static bool isStateMutabilitySpecifier(Value op) { return op == Pure || op == Constant || op == View || op == Payable; }
static bool isEtherSubdenomination(Value op) { return op == SubWei || op == SubSzabo || op == SubFinney || op == SubEther; }
static bool isTimeSubdenomination(Value op) { return op == SubSecond || op == SubMinute || op == SubHour || op == SubDay || op == SubWeek || op == SubYear; }
static bool isReservedKeyword(Value op) { return (Abstract <= op && op <= TypeOf); }