aboutsummaryrefslogtreecommitdiffstats
path: root/libsolidity
diff options
context:
space:
mode:
Diffstat (limited to 'libsolidity')
-rw-r--r--libsolidity/analysis/ConstantEvaluator.cpp6
-rw-r--r--libsolidity/analysis/ConstantEvaluator.h1
-rw-r--r--libsolidity/analysis/DeclarationContainer.cpp8
-rw-r--r--libsolidity/analysis/GlobalContext.cpp4
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.cpp9
-rw-r--r--libsolidity/analysis/PostTypeChecker.cpp36
-rw-r--r--libsolidity/analysis/PostTypeChecker.h5
-rw-r--r--libsolidity/analysis/StaticAnalyzer.cpp111
-rw-r--r--libsolidity/analysis/StaticAnalyzer.h6
-rw-r--r--libsolidity/analysis/SyntaxChecker.cpp37
-rw-r--r--libsolidity/analysis/SyntaxChecker.h2
-rw-r--r--libsolidity/analysis/TypeChecker.cpp253
-rw-r--r--libsolidity/analysis/TypeChecker.h7
-rw-r--r--libsolidity/analysis/ViewPureChecker.cpp7
-rw-r--r--libsolidity/ast/AST.cpp14
-rw-r--r--libsolidity/ast/AST.h41
-rw-r--r--libsolidity/ast/ASTAnnotations.h3
-rw-r--r--libsolidity/ast/ASTJsonConverter.cpp20
-rw-r--r--libsolidity/ast/ASTJsonConverter.h2
-rw-r--r--libsolidity/ast/AST_accept.h12
-rw-r--r--libsolidity/ast/Types.cpp302
-rw-r--r--libsolidity/ast/Types.h51
-rw-r--r--libsolidity/codegen/ABIFunctions.cpp3
-rw-r--r--libsolidity/codegen/ArrayUtils.cpp53
-rw-r--r--libsolidity/codegen/ArrayUtils.h6
-rw-r--r--libsolidity/codegen/CompilerContext.cpp32
-rw-r--r--libsolidity/codegen/CompilerContext.h9
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp284
-rw-r--r--libsolidity/codegen/CompilerUtils.h28
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp158
-rw-r--r--libsolidity/codegen/ContractCompiler.h6
-rw-r--r--libsolidity/codegen/ExpressionCompiler.cpp275
-rw-r--r--libsolidity/formal/SMTChecker.cpp49
-rw-r--r--libsolidity/formal/SSAVariable.cpp21
-rw-r--r--libsolidity/formal/SSAVariable.h8
-rw-r--r--libsolidity/formal/SolverInterface.h32
-rw-r--r--libsolidity/formal/SymbolicBoolVariable.cpp47
-rw-r--r--libsolidity/formal/SymbolicBoolVariable.h50
-rw-r--r--libsolidity/formal/SymbolicIntVariable.cpp12
-rw-r--r--libsolidity/formal/SymbolicIntVariable.h5
-rw-r--r--libsolidity/formal/SymbolicVariable.cpp6
-rw-r--r--libsolidity/formal/SymbolicVariable.h12
-rw-r--r--libsolidity/interface/CompilerStack.cpp143
-rw-r--r--libsolidity/interface/EVMVersion.h2
-rw-r--r--libsolidity/interface/ErrorReporter.cpp37
-rw-r--r--libsolidity/interface/ErrorReporter.h9
-rw-r--r--libsolidity/interface/GasEstimator.cpp9
-rw-r--r--libsolidity/parsing/DocStringParser.cpp10
-rw-r--r--libsolidity/parsing/Parser.cpp71
-rw-r--r--libsolidity/parsing/Parser.h7
-rw-r--r--libsolidity/parsing/Token.cpp2
51 files changed, 1741 insertions, 582 deletions
diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp
index 83f37f47..8659bbfd 100644
--- a/libsolidity/analysis/ConstantEvaluator.cpp
+++ b/libsolidity/analysis/ConstantEvaluator.cpp
@@ -87,6 +87,12 @@ void ConstantEvaluator::endVisit(Identifier const& _identifier)
setType(_identifier, type(*value));
}
+void ConstantEvaluator::endVisit(TupleExpression const& _tuple)
+{
+ if (!_tuple.isInlineArray() && _tuple.components().size() == 1)
+ setType(_tuple, type(*_tuple.components().front()));
+}
+
void ConstantEvaluator::setType(ASTNode const& _node, TypePointer const& _type)
{
if (_type && _type->category() == Type::Category::RationalNumber)
diff --git a/libsolidity/analysis/ConstantEvaluator.h b/libsolidity/analysis/ConstantEvaluator.h
index 77a357b6..ac3a24a1 100644
--- a/libsolidity/analysis/ConstantEvaluator.h
+++ b/libsolidity/analysis/ConstantEvaluator.h
@@ -56,6 +56,7 @@ private:
virtual void endVisit(UnaryOperation const& _operation);
virtual void endVisit(Literal const& _literal);
virtual void endVisit(Identifier const& _identifier);
+ virtual void endVisit(TupleExpression const& _tuple);
void setType(ASTNode const& _node, TypePointer const& _type);
TypePointer type(ASTNode const& _node);
diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp
index c7ba78d6..786272e4 100644
--- a/libsolidity/analysis/DeclarationContainer.cpp
+++ b/libsolidity/analysis/DeclarationContainer.cpp
@@ -45,7 +45,8 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
if (
dynamic_cast<FunctionDefinition const*>(&_declaration) ||
- dynamic_cast<EventDefinition const*>(&_declaration)
+ dynamic_cast<EventDefinition const*>(&_declaration) ||
+ dynamic_cast<MagicVariableDeclaration const*>(&_declaration)
)
{
// check that all other declarations with the same name are functions or a public state variable or events.
@@ -68,6 +69,11 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
!dynamic_cast<EventDefinition const*>(declaration)
)
return declaration;
+ if (
+ dynamic_cast<MagicVariableDeclaration const*>(&_declaration) &&
+ !dynamic_cast<MagicVariableDeclaration const*>(declaration)
+ )
+ return declaration;
// Or, continue.
}
}
diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp
index 34cb61d8..756bb540 100644
--- a/libsolidity/analysis/GlobalContext.cpp
+++ b/libsolidity/analysis/GlobalContext.cpp
@@ -35,9 +35,11 @@ namespace solidity
GlobalContext::GlobalContext():
m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{
+ make_shared<MagicVariableDeclaration>("abi", make_shared<MagicType>(MagicType::Kind::ABI)),
make_shared<MagicVariableDeclaration>("addmod", make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("assert", make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block)),
+ make_shared<MagicVariableDeclaration>("blockhash", make_shared<FunctionType>(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)),
make_shared<MagicVariableDeclaration>("ecrecover", make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("gasleft", make_shared<FunctionType>(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, false, StateMutability::View)),
make_shared<MagicVariableDeclaration>("keccak256", make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true, StateMutability::Pure)),
@@ -50,7 +52,9 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{
make_shared<MagicVariableDeclaration>("mulmod", make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("now", make_shared<IntegerType>(256)),
make_shared<MagicVariableDeclaration>("require", make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
+ make_shared<MagicVariableDeclaration>("require", make_shared<FunctionType>(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("revert", make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
+ make_shared<MagicVariableDeclaration>("revert", make_shared<FunctionType>(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("ripemd160", make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("selfdestruct", make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)),
make_shared<MagicVariableDeclaration>("sha256", make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true, StateMutability::Pure)),
diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp
index 2f675135..0a356f04 100644
--- a/libsolidity/analysis/NameAndTypeResolver.cpp
+++ b/libsolidity/analysis/NameAndTypeResolver.cpp
@@ -47,7 +47,9 @@ NameAndTypeResolver::NameAndTypeResolver(
if (!m_scopes[nullptr])
m_scopes[nullptr].reset(new DeclarationContainer());
for (Declaration const* declaration: _globals)
- m_scopes[nullptr]->registerDeclaration(*declaration);
+ {
+ solAssert(m_scopes[nullptr]->registerDeclaration(*declaration), "Unable to register global declaration.");
+ }
}
bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope)
@@ -202,8 +204,9 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
solAssert(
dynamic_cast<FunctionDefinition const*>(declaration) ||
dynamic_cast<EventDefinition const*>(declaration) ||
- dynamic_cast<VariableDeclaration const*>(declaration),
- "Found overloading involving something not a function or a variable."
+ dynamic_cast<VariableDeclaration const*>(declaration) ||
+ dynamic_cast<MagicVariableDeclaration const*>(declaration),
+ "Found overloading involving something not a function, event or a (magic) variable."
);
FunctionTypePointer functionType { declaration->functionType(false) };
diff --git a/libsolidity/analysis/PostTypeChecker.cpp b/libsolidity/analysis/PostTypeChecker.cpp
index fbc72e52..19d0b708 100644
--- a/libsolidity/analysis/PostTypeChecker.cpp
+++ b/libsolidity/analysis/PostTypeChecker.cpp
@@ -21,6 +21,8 @@
#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/interface/Version.h>
+#include <libdevcore/Algorithms.h>
+
#include <boost/range/adaptor/map.hpp>
#include <memory>
@@ -47,7 +49,7 @@ void PostTypeChecker::endVisit(ContractDefinition const&)
{
solAssert(!m_currentConstVariable, "");
for (auto declaration: m_constVariables)
- if (auto identifier = findCycle(declaration))
+ if (auto identifier = findCycle(*declaration))
m_errorReporter.typeError(
declaration->location(),
"The value of the constant " + declaration->name() +
@@ -87,20 +89,24 @@ bool PostTypeChecker::visit(Identifier const& _identifier)
return true;
}
-VariableDeclaration const* PostTypeChecker::findCycle(
- VariableDeclaration const* _startingFrom,
- set<VariableDeclaration const*> const& _seen
-)
+VariableDeclaration const* PostTypeChecker::findCycle(VariableDeclaration const& _startingFrom)
{
- if (_seen.count(_startingFrom))
- return _startingFrom;
- else if (m_constVariableDependencies.count(_startingFrom))
+ auto visitor = [&](VariableDeclaration const& _variable, CycleDetector<VariableDeclaration>& _cycleDetector)
{
- set<VariableDeclaration const*> seen(_seen);
- seen.insert(_startingFrom);
- for (auto v: m_constVariableDependencies[_startingFrom])
- if (findCycle(v, seen))
- return v;
- }
- return nullptr;
+ // Iterating through the dependencies needs to be deterministic and thus cannot
+ // depend on the memory layout.
+ // Because of that, we sort by AST node id.
+ vector<VariableDeclaration const*> dependencies(
+ m_constVariableDependencies[&_variable].begin(),
+ m_constVariableDependencies[&_variable].end()
+ );
+ sort(dependencies.begin(), dependencies.end(), [](VariableDeclaration const* _a, VariableDeclaration const* _b) -> bool
+ {
+ return _a->id() < _b->id();
+ });
+ for (auto v: dependencies)
+ if (_cycleDetector.run(*v))
+ return;
+ };
+ return CycleDetector<VariableDeclaration>(visitor).run(_startingFrom);
}
diff --git a/libsolidity/analysis/PostTypeChecker.h b/libsolidity/analysis/PostTypeChecker.h
index bafc1ae6..4f9dac6e 100644
--- a/libsolidity/analysis/PostTypeChecker.h
+++ b/libsolidity/analysis/PostTypeChecker.h
@@ -55,10 +55,7 @@ private:
virtual bool visit(Identifier const& _identifier) override;
- VariableDeclaration const* findCycle(
- VariableDeclaration const* _startingFrom,
- std::set<VariableDeclaration const*> const& _seen = std::set<VariableDeclaration const*>{}
- );
+ VariableDeclaration const* findCycle(VariableDeclaration const& _startingFrom);
ErrorReporter& m_errorReporter;
diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp
index d4de219a..00a581d0 100644
--- a/libsolidity/analysis/StaticAnalyzer.cpp
+++ b/libsolidity/analysis/StaticAnalyzer.cpp
@@ -21,6 +21,7 @@
*/
#include <libsolidity/analysis/StaticAnalyzer.h>
+#include <libsolidity/analysis/ConstantEvaluator.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <memory>
@@ -50,6 +51,16 @@ void StaticAnalyzer::endVisit(ContractDefinition const&)
bool StaticAnalyzer::visit(FunctionDefinition const& _function)
{
+ const bool isInterface = m_currentContract->contractKind() == ContractDefinition::ContractKind::Interface;
+
+ if (_function.noVisibilitySpecified())
+ m_errorReporter.warning(
+ _function.location(),
+ "No visibility specified. Defaulting to \"" +
+ Declaration::visibilityToString(_function.visibility()) +
+ "\". " +
+ (isInterface ? "In interfaces it defaults to external." : "")
+ );
if (_function.isImplemented())
m_currentFunction = &_function;
else
@@ -68,13 +79,13 @@ void StaticAnalyzer::endVisit(FunctionDefinition const&)
for (auto const& var: m_localVarUseCount)
if (var.second == 0)
{
- if (var.first->isCallableParameter())
+ if (var.first.second->isCallableParameter())
m_errorReporter.warning(
- var.first->location(),
+ var.first.second->location(),
"Unused function parameter. Remove or comment out the variable name to silence this warning."
);
else
- m_errorReporter.warning(var.first->location(), "Unused local variable.");
+ m_errorReporter.warning(var.first.second->location(), "Unused local variable.");
}
m_localVarUseCount.clear();
@@ -87,7 +98,7 @@ bool StaticAnalyzer::visit(Identifier const& _identifier)
{
solAssert(!var->name().empty(), "");
if (var->isLocalVariable())
- m_localVarUseCount[var] += 1;
+ m_localVarUseCount[make_pair(var->id(), var)] += 1;
}
return true;
}
@@ -99,7 +110,7 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
solAssert(_variable.isLocalVariable(), "");
if (_variable.name() != "")
// This is not a no-op, the entry might pre-exist.
- m_localVarUseCount[&_variable] += 0;
+ m_localVarUseCount[make_pair(_variable.id(), &_variable)] += 0;
}
else if (_variable.isStateVariable())
{
@@ -122,7 +133,7 @@ bool StaticAnalyzer::visit(Return const& _return)
if (m_currentFunction && _return.expression())
for (auto const& var: m_currentFunction->returnParameters())
if (!var->name().empty())
- m_localVarUseCount[var.get()] += 1;
+ m_localVarUseCount[make_pair(var->id(), var.get())] += 1;
return true;
}
@@ -142,6 +153,7 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
bool const v050 = m_currentContract->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get()))
+ {
if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "gas")
{
if (v050)
@@ -155,6 +167,20 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
"\"msg.gas\" has been deprecated in favor of \"gasleft()\""
);
}
+ if (type->kind() == MagicType::Kind::Block && _memberAccess.memberName() == "blockhash")
+ {
+ if (v050)
+ m_errorReporter.typeError(
+ _memberAccess.location(),
+ "\"block.blockhash()\" has been deprecated in favor of \"blockhash()\""
+ );
+ else
+ m_errorReporter.warning(
+ _memberAccess.location(),
+ "\"block.blockhash()\" has been deprecated in favor of \"blockhash()\""
+ );
+ }
+ }
if (m_nonPayablePublic && !m_library)
if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get()))
@@ -180,10 +206,32 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
);
}
- if (m_constructor && m_currentContract)
- if (ContractType const* type = dynamic_cast<ContractType const*>(_memberAccess.expression().annotation().type.get()))
- if (type->contractDefinition() == *m_currentContract)
- m_errorReporter.warning(_memberAccess.location(), "\"this\" used in constructor.");
+ if (m_constructor)
+ {
+ auto const* expr = &_memberAccess.expression();
+ while(expr)
+ {
+ if (auto id = dynamic_cast<Identifier const*>(expr))
+ {
+ if (id->name() == "this")
+ m_errorReporter.warning(
+ id->location(),
+ "\"this\" used in constructor. "
+ "Note that external functions of a contract "
+ "cannot be called while it is being constructed.");
+ break;
+ }
+ else if (auto tuple = dynamic_cast<TupleExpression const*>(expr))
+ {
+ if (tuple->components().size() == 1)
+ expr = tuple->components().front().get();
+ else
+ break;
+ }
+ else
+ break;
+ }
+ }
return true;
}
@@ -199,13 +247,54 @@ bool StaticAnalyzer::visit(InlineAssembly const& _inlineAssembly)
{
solAssert(!var->name().empty(), "");
if (var->isLocalVariable())
- m_localVarUseCount[var] += 1;
+ m_localVarUseCount[make_pair(var->id(), var)] += 1;
}
}
return true;
}
+bool StaticAnalyzer::visit(BinaryOperation const& _operation)
+{
+ if (
+ _operation.rightExpression().annotation().isPure &&
+ (_operation.getOperator() == Token::Div || _operation.getOperator() == Token::Mod)
+ )
+ if (auto rhs = dynamic_pointer_cast<RationalNumberType const>(
+ ConstantEvaluator(m_errorReporter).evaluate(_operation.rightExpression())
+ ))
+ if (rhs->isZero())
+ m_errorReporter.typeError(
+ _operation.location(),
+ (_operation.getOperator() == Token::Div) ? "Division by zero." : "Modulo zero."
+ );
+
+ return true;
+}
+
+bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
+{
+ if (_functionCall.annotation().kind == FunctionCallKind::FunctionCall)
+ {
+ auto functionType = dynamic_pointer_cast<FunctionType const>(_functionCall.expression().annotation().type);
+ solAssert(functionType, "");
+ if (functionType->kind() == FunctionType::Kind::AddMod || functionType->kind() == FunctionType::Kind::MulMod)
+ {
+ solAssert(_functionCall.arguments().size() == 3, "");
+ if (_functionCall.arguments()[2]->annotation().isPure)
+ if (auto lastArg = dynamic_pointer_cast<RationalNumberType const>(
+ ConstantEvaluator(m_errorReporter).evaluate(*(_functionCall.arguments())[2])
+ ))
+ if (lastArg->isZero())
+ m_errorReporter.typeError(
+ _functionCall.location(),
+ "Arithmetic modulo zero."
+ );
+ }
+ }
+ return true;
+}
+
bigint StaticAnalyzer::structureSizeEstimate(Type const& _type, set<StructDefinition const*>& _structsSeen)
{
switch (_type.category())
diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h
index 124c4e7c..2a62e391 100644
--- a/libsolidity/analysis/StaticAnalyzer.h
+++ b/libsolidity/analysis/StaticAnalyzer.h
@@ -64,6 +64,8 @@ private:
virtual bool visit(Return const& _return) override;
virtual bool visit(MemberAccess const& _memberAccess) override;
virtual bool visit(InlineAssembly const& _inlineAssembly) override;
+ virtual bool visit(BinaryOperation const& _operation) override;
+ virtual bool visit(FunctionCall const& _functionCall) override;
/// @returns the size of this type in storage, including all sub-types.
static bigint structureSizeEstimate(Type const& _type, std::set<StructDefinition const*>& _structsSeen);
@@ -77,7 +79,9 @@ private:
bool m_nonPayablePublic = false;
/// Number of uses of each (named) local variable in a function, counter is initialized with zero.
- std::map<VariableDeclaration const*, int> m_localVarUseCount;
+ /// Pairs of AST ids and pointers are used as keys to ensure a deterministic order
+ /// when traversing.
+ std::map<std::pair<size_t, VariableDeclaration const*>, int> m_localVarUseCount;
FunctionDefinition const* m_currentFunction = nullptr;
diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp
index ddac194b..f648e5b4 100644
--- a/libsolidity/analysis/SyntaxChecker.cpp
+++ b/libsolidity/analysis/SyntaxChecker.cpp
@@ -214,18 +214,31 @@ bool SyntaxChecker::visit(FunctionDefinition const& _function)
{
bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050);
- if (_function.noVisibilitySpecified())
+ if (v050 && _function.noVisibilitySpecified())
+ m_errorReporter.syntaxError(_function.location(), "No visibility specified.");
+
+ if (_function.isOldStyleConstructor())
{
if (v050)
- m_errorReporter.syntaxError(_function.location(), "No visibility specified.");
+ m_errorReporter.syntaxError(
+ _function.location(),
+ "Functions are not allowed to have the same name as the contract. "
+ "If you intend this to be a constructor, use \"constructor(...) { ... }\" to define it."
+ );
else
m_errorReporter.warning(
_function.location(),
- "No visibility specified. Defaulting to \"" +
- Declaration::visibilityToString(_function.visibility()) +
- "\"."
+ "Defining constructors as functions with the same name as the contract is deprecated. "
+ "Use \"constructor(...) { ... }\" instead."
);
}
+ if (!_function.isImplemented() && !_function.modifiers().empty())
+ {
+ if (v050)
+ m_errorReporter.syntaxError(_function.location(), "Functions without implementation cannot have modifiers.");
+ else
+ m_errorReporter.warning( _function.location(), "Modifiers of functions without implementation are ignored." );
+ }
return true;
}
@@ -255,3 +268,17 @@ bool SyntaxChecker::visit(VariableDeclaration const& _declaration)
}
return true;
}
+
+bool SyntaxChecker::visit(StructDefinition const& _struct)
+{
+ bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050);
+
+ if (_struct.members().empty())
+ {
+ if (v050)
+ m_errorReporter.syntaxError(_struct.location(), "Defining empty structs is disallowed.");
+ else
+ m_errorReporter.warning(_struct.location(), "Defining empty structs is deprecated.");
+ }
+ return true;
+}
diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h
index 871bf0a9..1579df57 100644
--- a/libsolidity/analysis/SyntaxChecker.h
+++ b/libsolidity/analysis/SyntaxChecker.h
@@ -71,6 +71,8 @@ private:
virtual bool visit(VariableDeclaration const& _declaration) override;
+ virtual bool visit(StructDefinition const& _struct) override;
+
ErrorReporter& m_errorReporter;
/// Flag that indicates whether a function modifier actually contains '_'.
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index bebdb9b6..47a551dc 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -60,17 +60,7 @@ bool typeSupportedByOldABIEncoder(Type const& _type)
bool TypeChecker::checkTypeRequirements(ASTNode const& _contract)
{
- try
- {
- _contract.accept(*this);
- }
- catch (FatalError const&)
- {
- // We got a fatal error which required to stop further type checking, but we can
- // continue normally from here.
- if (m_errorReporter.errors().empty())
- throw; // Something is weird here, rather throw again.
- }
+ _contract.accept(*this);
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
@@ -101,7 +91,7 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
checkContractDuplicateEvents(_contract);
checkContractIllegalOverrides(_contract);
checkContractAbstractFunctions(_contract);
- checkContractAbstractConstructors(_contract);
+ checkContractBaseConstructorArguments(_contract);
FunctionDefinition const* function = _contract.constructor();
if (function)
@@ -291,42 +281,108 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont
}
}
-void TypeChecker::checkContractAbstractConstructors(ContractDefinition const& _contract)
+void TypeChecker::checkContractBaseConstructorArguments(ContractDefinition const& _contract)
{
- set<ContractDefinition const*> argumentsNeeded;
- // check that we get arguments for all base constructors that need it.
- // If not mark the contract as abstract (not fully implemented)
+ bool const v050 = _contract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
vector<ContractDefinition const*> const& bases = _contract.annotation().linearizedBaseContracts;
- for (ContractDefinition const* contract: bases)
- if (FunctionDefinition const* constructor = contract->constructor())
- if (contract != &_contract && !constructor->parameters().empty())
- argumentsNeeded.insert(contract);
+ // Determine the arguments that are used for the base constructors.
for (ContractDefinition const* contract: bases)
{
if (FunctionDefinition const* constructor = contract->constructor())
for (auto const& modifier: constructor->modifiers())
{
- auto baseContract = dynamic_cast<ContractDefinition const*>(
- &dereference(*modifier->name())
- );
- if (baseContract)
- argumentsNeeded.erase(baseContract);
+ auto baseContract = dynamic_cast<ContractDefinition const*>(&dereference(*modifier->name()));
+ if (modifier->arguments())
+ {
+ if (baseContract && baseContract->constructor())
+ annotateBaseConstructorArguments(_contract, baseContract->constructor(), modifier.get());
+ }
+ else
+ {
+ if (v050)
+ m_errorReporter.declarationError(
+ modifier->location(),
+ "Modifier-style base constructor call without arguments."
+ );
+ else
+ m_errorReporter.warning(
+ modifier->location(),
+ "Modifier-style base constructor call without arguments."
+ );
+ }
}
-
for (ASTPointer<InheritanceSpecifier> const& base: contract->baseContracts())
{
auto baseContract = dynamic_cast<ContractDefinition const*>(&dereference(base->name()));
solAssert(baseContract, "");
- if (!base->arguments().empty())
- argumentsNeeded.erase(baseContract);
+
+ if (baseContract->constructor() && base->arguments() && !base->arguments()->empty())
+ annotateBaseConstructorArguments(_contract, baseContract->constructor(), base.get());
+ }
+ }
+
+ // check that we get arguments for all base constructors that need it.
+ // If not mark the contract as abstract (not fully implemented)
+ for (ContractDefinition const* contract: bases)
+ if (FunctionDefinition const* constructor = contract->constructor())
+ if (contract != &_contract && !constructor->parameters().empty())
+ if (!_contract.annotation().baseConstructorArguments.count(constructor))
+ _contract.annotation().unimplementedFunctions.push_back(constructor);
+}
+
+void TypeChecker::annotateBaseConstructorArguments(
+ ContractDefinition const& _currentContract,
+ FunctionDefinition const* _baseConstructor,
+ ASTNode const* _argumentNode
+)
+{
+ bool const v050 = _currentContract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
+
+ solAssert(_baseConstructor, "");
+ solAssert(_argumentNode, "");
+
+ auto insertionResult = _currentContract.annotation().baseConstructorArguments.insert(
+ std::make_pair(_baseConstructor, _argumentNode)
+ );
+ if (!insertionResult.second)
+ {
+ ASTNode const* previousNode = insertionResult.first->second;
+
+ SourceLocation const* mainLocation = nullptr;
+ SecondarySourceLocation ssl;
+
+ if (
+ _currentContract.location().contains(previousNode->location()) ||
+ _currentContract.location().contains(_argumentNode->location())
+ )
+ {
+ mainLocation = &previousNode->location();
+ ssl.append("Second constructor call is here:", _argumentNode->location());
}
+ else
+ {
+ mainLocation = &_currentContract.location();
+ ssl.append("First constructor call is here: ", _argumentNode->location());
+ ssl.append("Second constructor call is here: ", previousNode->location());
+ }
+
+ if (v050)
+ m_errorReporter.declarationError(
+ *mainLocation,
+ ssl,
+ "Base constructor arguments given twice."
+ );
+ else
+ m_errorReporter.warning(
+ *mainLocation,
+ "Base constructor arguments given twice.",
+ ssl
+ );
}
- if (!argumentsNeeded.empty())
- for (ContractDefinition const* contract: argumentsNeeded)
- _contract.annotation().unimplementedFunctions.push_back(contract->constructor());
+
}
void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contract)
@@ -378,7 +434,16 @@ void TypeChecker::checkFunctionOverride(FunctionDefinition const& function, Func
function.annotation().superFunction = &super;
if (function.visibility() != super.visibility())
+ {
+ // visibility is enforced to be external in interfaces, but a contract can override that with public
+ if (
+ super.inContractKind() == ContractDefinition::ContractKind::Interface &&
+ function.inContractKind() != ContractDefinition::ContractKind::Interface &&
+ function.visibility() == FunctionDefinition::Visibility::Public
+ )
+ return;
overrideError(function, super, "Overriding function visibility differs.");
+ }
else if (function.stateMutability() != super.stateMutability())
overrideError(
@@ -497,30 +562,46 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
// Interfaces do not have constructors, so there are zero parameters.
parameterTypes = ContractType(*base).newExpressionType()->parameterTypes();
- if (!arguments.empty() && parameterTypes.size() != arguments.size())
+ if (arguments)
{
- m_errorReporter.typeError(
- _inheritance.location(),
- "Wrong argument count for constructor call: " +
- toString(arguments.size()) +
- " arguments given but expected " +
- toString(parameterTypes.size()) +
- "."
- );
- return;
- }
+ bool v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
- for (size_t i = 0; i < arguments.size(); ++i)
- if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
- m_errorReporter.typeError(
- arguments[i]->location(),
- "Invalid type for argument in constructor call. "
- "Invalid implicit conversion from " +
- type(*arguments[i])->toString() +
- " to " +
- parameterTypes[i]->toString() +
- " requested."
- );
+ if (parameterTypes.size() != arguments->size())
+ {
+ if (arguments->size() == 0 && !v050)
+ m_errorReporter.warning(
+ _inheritance.location(),
+ "Wrong argument count for constructor call: " +
+ toString(arguments->size()) +
+ " arguments given but expected " +
+ toString(parameterTypes.size()) +
+ "."
+ );
+ else
+ {
+ m_errorReporter.typeError(
+ _inheritance.location(),
+ "Wrong argument count for constructor call: " +
+ toString(arguments->size()) +
+ " arguments given but expected " +
+ toString(parameterTypes.size()) +
+ "."
+ );
+ return;
+ }
+ }
+ for (size_t i = 0; i < arguments->size(); ++i)
+ if (!type(*(*arguments)[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
+ m_errorReporter.typeError(
+ (*arguments)[i]->location(),
+ "Invalid type for argument in constructor call. "
+ "Invalid implicit conversion from " +
+ type(*(*arguments)[i])->toString() +
+ " to " +
+ parameterTypes[i]->toString() +
+ " requested."
+ );
+ }
}
void TypeChecker::endVisit(UsingForDirective const& _usingFor)
@@ -731,7 +812,8 @@ void TypeChecker::visitManually(
vector<ContractDefinition const*> const& _bases
)
{
- std::vector<ASTPointer<Expression>> const& arguments = _modifier.arguments();
+ std::vector<ASTPointer<Expression>> const& arguments =
+ _modifier.arguments() ? *_modifier.arguments() : std::vector<ASTPointer<Expression>>();
for (ASTPointer<Expression> const& argument: arguments)
argument->accept(*this);
_modifier.name()->accept(*this);
@@ -769,7 +851,7 @@ void TypeChecker::visitManually(
);
return;
}
- for (size_t i = 0; i < _modifier.arguments().size(); ++i)
+ for (size_t i = 0; i < arguments.size(); ++i)
if (!type(*arguments[i])->isImplicitlyConvertibleTo(*type(*(*parameters)[i])))
m_errorReporter.typeError(
arguments[i]->location(),
@@ -1551,16 +1633,22 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
_functionCall.expression().annotation().isPure &&
functionType->isPure();
+ bool allowDynamicTypes = m_evmVersion.supportsReturndata();
if (!functionType)
{
m_errorReporter.typeError(_functionCall.location(), "Type is not callable");
_functionCall.annotation().type = make_shared<TupleType>();
return false;
}
- else if (functionType->returnParameterTypes().size() == 1)
- _functionCall.annotation().type = functionType->returnParameterTypes().front();
+
+ auto returnTypes =
+ allowDynamicTypes ?
+ functionType->returnParameterTypes() :
+ functionType->returnParameterTypesWithoutDynamicTypes();
+ if (returnTypes.size() == 1)
+ _functionCall.annotation().type = returnTypes.front();
else
- _functionCall.annotation().type = make_shared<TupleType>(functionType->returnParameterTypes());
+ _functionCall.annotation().type = make_shared<TupleType>(returnTypes);
if (auto functionName = dynamic_cast<Identifier const*>(&_functionCall.expression()))
{
@@ -1600,7 +1688,19 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
}
}
- if (!functionType->takesArbitraryParameters() && parameterTypes.size() != arguments.size())
+ if (functionType->takesArbitraryParameters() && arguments.size() < parameterTypes.size())
+ {
+ solAssert(_functionCall.annotation().kind == FunctionCallKind::FunctionCall, "");
+ m_errorReporter.typeError(
+ _functionCall.location(),
+ "Need at least " +
+ toString(parameterTypes.size()) +
+ " arguments for function call, but provided only " +
+ toString(arguments.size()) +
+ "."
+ );
+ }
+ else if (!functionType->takesArbitraryParameters() && parameterTypes.size() != arguments.size())
{
bool isStructConstructorCall = _functionCall.annotation().kind == FunctionCallKind::StructConstructorCall;
@@ -1623,15 +1723,36 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
}
else if (isPositionalCall)
{
- // call by positional arguments
+ bool const abiEncodeV2 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2);
+
for (size_t i = 0; i < arguments.size(); ++i)
{
auto const& argType = type(*arguments[i]);
- if (functionType->takesArbitraryParameters())
+ if (functionType->takesArbitraryParameters() && i >= parameterTypes.size())
{
+ bool errored = false;
if (auto t = dynamic_cast<RationalNumberType const*>(argType.get()))
if (!t->mobileType())
+ {
m_errorReporter.typeError(arguments[i]->location(), "Invalid rational number (too large or division by zero).");
+ errored = true;
+ }
+ if (!errored)
+ {
+ TypePointer encodingType;
+ if (
+ argType->mobileType() &&
+ argType->mobileType()->interfaceType(false) &&
+ argType->mobileType()->interfaceType(false)->encodingType()
+ )
+ encodingType = argType->mobileType()->interfaceType(false)->encodingType();
+ // Structs are fine as long as ABIV2 is activated and we do not do packed encoding.
+ if (!encodingType || (
+ dynamic_cast<StructType const*>(encodingType.get()) &&
+ !(abiEncodeV2 && functionType->padArguments())
+ ))
+ m_errorReporter.typeError(arguments[i]->location(), "This type cannot be encoded.");
+ }
}
else if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
m_errorReporter.typeError(
@@ -1867,7 +1988,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
m_errorReporter.warning(
_memberAccess.location(),
"Using contract member \"" + memberName +"\" inherited from the address type is deprecated." +
- " Convert the contract to \"address\" type to access the member."
+ " Convert the contract to \"address\" type to access the member,"
+ " for example use \"address(contract)." + memberName + "\" instead."
);
}
@@ -2025,10 +2147,9 @@ bool TypeChecker::visit(Identifier const& _identifier)
for (Declaration const* declaration: annotation.overloadedDeclarations)
{
- TypePointer function = declaration->type();
- solAssert(!!function, "Requested type not present.");
- auto const* functionType = dynamic_cast<FunctionType const*>(function.get());
- if (functionType && functionType->canTakeArguments(*annotation.argumentTypes))
+ FunctionTypePointer functionType = declaration->functionType(true);
+ solAssert(!!functionType, "Requested type not present.");
+ if (functionType->canTakeArguments(*annotation.argumentTypes))
candidates.push_back(declaration);
}
if (candidates.empty())
diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h
index 2ba31232..2245abd6 100644
--- a/libsolidity/analysis/TypeChecker.h
+++ b/libsolidity/analysis/TypeChecker.h
@@ -73,7 +73,12 @@ private:
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);
+ void checkContractBaseConstructorArguments(ContractDefinition const& _contract);
+ void annotateBaseConstructorArguments(
+ ContractDefinition const& _currentContract,
+ FunctionDefinition const* _baseConstructor,
+ ASTNode const* _argumentNode
+ );
/// Checks that different functions with external visibility end up having different
/// external argument types (i.e. different signature).
void checkContractExternalTypeClashes(ContractDefinition const& _contract);
diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp
index 13c3ab68..d9843012 100644
--- a/libsolidity/analysis/ViewPureChecker.cpp
+++ b/libsolidity/analysis/ViewPureChecker.cpp
@@ -305,10 +305,15 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
mutability = StateMutability::View;
break;
case Type::Category::Magic:
+ {
// we can ignore the kind of magic and only look at the name of the member
- if (member != "data" && member != "sig" && member != "blockhash")
+ set<string> static const pureMembers{
+ "encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "data", "sig", "blockhash"
+ };
+ if (!pureMembers.count(member))
mutability = StateMutability::View;
break;
+ }
case Type::Category::Struct:
{
if (_memberAccess.expression().annotation().type->dataStoredIn(DataLocation::Storage))
diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp
index 27220b1f..80f5d642 100644
--- a/libsolidity/ast/AST.cpp
+++ b/libsolidity/ast/AST.cpp
@@ -290,7 +290,14 @@ TypeDeclarationAnnotation& EnumDefinition::annotation() const
return dynamic_cast<TypeDeclarationAnnotation&>(*m_annotation);
}
-shared_ptr<FunctionType> FunctionDefinition::functionType(bool _internal) const
+ContractDefinition::ContractKind FunctionDefinition::inContractKind() const
+{
+ auto contractDef = dynamic_cast<ContractDefinition const*>(scope());
+ solAssert(contractDef, "Enclosing Scope of FunctionDefinition was not set.");
+ return contractDef->contractKind();
+}
+
+FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
{
if (_internal)
{
@@ -331,6 +338,7 @@ shared_ptr<FunctionType> FunctionDefinition::functionType(bool _internal) const
TypePointer FunctionDefinition::type() const
{
+ solAssert(visibility() != Declaration::Visibility::External, "");
return make_shared<FunctionType>(*this);
}
@@ -372,7 +380,7 @@ TypePointer EventDefinition::type() const
return make_shared<FunctionType>(*this);
}
-std::shared_ptr<FunctionType> EventDefinition::functionType(bool _internal) const
+FunctionTypePointer EventDefinition::functionType(bool _internal) const
{
if (_internal)
return make_shared<FunctionType>(*this);
@@ -477,7 +485,7 @@ TypePointer VariableDeclaration::type() const
return annotation().type;
}
-shared_ptr<FunctionType> VariableDeclaration::functionType(bool _internal) const
+FunctionTypePointer VariableDeclaration::functionType(bool _internal) const
{
if (_internal)
return {};
diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h
index 863ad2fe..a53987bf 100644
--- a/libsolidity/ast/AST.h
+++ b/libsolidity/ast/AST.h
@@ -203,6 +203,7 @@ public:
bool isPublic() const { return visibility() >= Visibility::Public; }
virtual bool isVisibleInContract() const { return visibility() != Visibility::External; }
bool isVisibleInDerivedContracts() const { return isVisibleInContract() && visibility() >= Visibility::Internal; }
+ bool isVisibleAsLibraryMember() const { return visibility() >= Visibility::Internal; }
std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); }
@@ -217,7 +218,7 @@ public:
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
/// @returns null when it is not accessible as a function.
- virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const { return {}; }
+ virtual FunctionTypePointer functionType(bool /*_internal*/) const { return {}; }
protected:
virtual Visibility defaultVisibility() const { return Visibility::Public; }
@@ -424,19 +425,22 @@ public:
InheritanceSpecifier(
SourceLocation const& _location,
ASTPointer<UserDefinedTypeName> const& _baseName,
- std::vector<ASTPointer<Expression>> _arguments
+ std::unique_ptr<std::vector<ASTPointer<Expression>>> _arguments
):
- ASTNode(_location), m_baseName(_baseName), m_arguments(_arguments) {}
+ ASTNode(_location), m_baseName(_baseName), m_arguments(std::move(_arguments)) {}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
UserDefinedTypeName const& name() const { return *m_baseName; }
- std::vector<ASTPointer<Expression>> const& arguments() const { return m_arguments; }
+ // Returns nullptr if no argument list was given (``C``).
+ // If an argument list is given (``C(...)``), the arguments are returned
+ // as a vector of expressions. Note that this vector can be empty (``C()``).
+ std::vector<ASTPointer<Expression>> const* arguments() const { return m_arguments.get(); }
private:
ASTPointer<UserDefinedTypeName> m_baseName;
- std::vector<ASTPointer<Expression>> m_arguments;
+ std::unique_ptr<std::vector<ASTPointer<Expression>>> m_arguments;
};
/**
@@ -606,7 +610,8 @@ public:
StateMutability stateMutability() const { return m_stateMutability; }
bool isConstructor() const { return m_isConstructor; }
- bool isFallback() const { return name().empty(); }
+ bool isOldStyleConstructor() const { return m_isConstructor && !name().empty(); }
+ bool isFallback() const { return !m_isConstructor && name().empty(); }
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(); }
@@ -623,11 +628,13 @@ public:
/// arguments separated by commas all enclosed in parentheses without any spaces.
std::string externalSignature() const;
+ ContractDefinition::ContractKind inContractKind() const;
+
virtual TypePointer type() const override;
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
/// @returns null when it is not accessible as a function.
- virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override;
+ virtual FunctionTypePointer functionType(bool /*_internal*/) const override;
virtual FunctionDefinitionAnnotation& annotation() const override;
@@ -696,7 +703,7 @@ public:
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
/// @returns null when it is not accessible as a function.
- virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override;
+ virtual FunctionTypePointer functionType(bool /*_internal*/) const override;
virtual VariableDeclarationAnnotation& annotation() const override;
@@ -755,19 +762,22 @@ public:
ModifierInvocation(
SourceLocation const& _location,
ASTPointer<Identifier> const& _name,
- std::vector<ASTPointer<Expression>> _arguments
+ std::unique_ptr<std::vector<ASTPointer<Expression>>> _arguments
):
- ASTNode(_location), m_modifierName(_name), m_arguments(_arguments) {}
+ ASTNode(_location), m_modifierName(_name), m_arguments(std::move(_arguments)) {}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
ASTPointer<Identifier> const& name() const { return m_modifierName; }
- std::vector<ASTPointer<Expression>> const& arguments() const { return m_arguments; }
+ // Returns nullptr if no argument list was given (``mod``).
+ // If an argument list is given (``mod(...)``), the arguments are returned
+ // as a vector of expressions. Note that this vector can be empty (``mod()``).
+ std::vector<ASTPointer<Expression>> const* arguments() const { return m_arguments.get(); }
private:
ASTPointer<Identifier> m_modifierName;
- std::vector<ASTPointer<Expression>> m_arguments;
+ std::unique_ptr<std::vector<ASTPointer<Expression>>> m_arguments;
};
/**
@@ -795,7 +805,7 @@ public:
bool isAnonymous() const { return m_anonymous; }
virtual TypePointer type() const override;
- virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override;
+ virtual FunctionTypePointer functionType(bool /*_internal*/) const override;
virtual EventDefinitionAnnotation& annotation() const override;
@@ -821,6 +831,11 @@ public:
solAssert(false, "MagicVariableDeclaration used inside real AST.");
}
+ virtual FunctionTypePointer functionType(bool) const override
+ {
+ solAssert(m_type->category() == Type::Category::Function, "");
+ return std::dynamic_pointer_cast<FunctionType const>(m_type);
+ }
virtual TypePointer type() const override { return m_type; }
private:
diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h
index 3d4236cc..5cbe42bd 100644
--- a/libsolidity/ast/ASTAnnotations.h
+++ b/libsolidity/ast/ASTAnnotations.h
@@ -90,6 +90,9 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnota
/// List of contracts this contract creates, i.e. which need to be compiled first.
/// Also includes all contracts from @a linearizedBaseContracts.
std::set<ContractDefinition const*> contractDependencies;
+ /// Mapping containing the nodes that define the arguments for base constructors.
+ /// These can either be inheritance specifiers or modifier invocations.
+ std::map<FunctionDefinition const*, ASTNode const*> baseConstructorArguments;
};
struct FunctionDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp
index 4fef67c3..b8e00b60 100644
--- a/libsolidity/ast/ASTJsonConverter.cpp
+++ b/libsolidity/ast/ASTJsonConverter.cpp
@@ -134,10 +134,10 @@ string ASTJsonConverter::namePathToString(std::vector<ASTString> const& _namePat
return boost::algorithm::join(_namePath, ".");
}
-Json::Value ASTJsonConverter::typePointerToJson(TypePointer _tp)
+Json::Value ASTJsonConverter::typePointerToJson(TypePointer _tp, bool _short)
{
Json::Value typeDescriptions(Json::objectValue);
- typeDescriptions["typeString"] = _tp ? Json::Value(_tp->toString()) : Json::nullValue;
+ typeDescriptions["typeString"] = _tp ? Json::Value(_tp->toString(_short)) : Json::nullValue;
typeDescriptions["typeIdentifier"] = _tp ? Json::Value(_tp->identifier()) : Json::nullValue;
return typeDescriptions;
@@ -268,7 +268,7 @@ bool ASTJsonConverter::visit(InheritanceSpecifier const& _node)
{
setJsonNode(_node, "InheritanceSpecifier", {
make_pair("baseName", toJson(_node.name())),
- make_pair("arguments", toJson(_node.arguments()))
+ make_pair("arguments", _node.arguments() ? toJson(*_node.arguments()) : Json::nullValue)
});
return false;
}
@@ -354,7 +354,7 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node)
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))
+ make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
};
if (m_inEvent)
attributes.push_back(make_pair("indexed", _node.isIndexed()));
@@ -378,7 +378,7 @@ bool ASTJsonConverter::visit(ModifierInvocation const& _node)
{
setJsonNode(_node, "ModifierInvocation", {
make_pair("modifierName", toJson(*_node.name())),
- make_pair("arguments", toJson(_node.arguments()))
+ make_pair("arguments", _node.arguments() ? toJson(*_node.arguments()) : Json::nullValue)
});
return false;
}
@@ -399,7 +399,7 @@ bool ASTJsonConverter::visit(ElementaryTypeName const& _node)
{
setJsonNode(_node, "ElementaryTypeName", {
make_pair("name", _node.typeName().toString()),
- make_pair("typeDescriptions", typePointerToJson(_node.annotation().type))
+ make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
});
return false;
}
@@ -410,7 +410,7 @@ bool ASTJsonConverter::visit(UserDefinedTypeName const& _node)
make_pair("name", namePathToString(_node.namePath())),
make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)),
make_pair("contractScope", idOrNull(_node.annotation().contractScope)),
- make_pair("typeDescriptions", typePointerToJson(_node.annotation().type))
+ make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
});
return false;
}
@@ -425,7 +425,7 @@ bool ASTJsonConverter::visit(FunctionTypeName const& _node)
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))
+ make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
});
return false;
}
@@ -435,7 +435,7 @@ bool ASTJsonConverter::visit(Mapping const& _node)
setJsonNode(_node, "Mapping", {
make_pair("keyType", toJson(_node.keyType())),
make_pair("valueType", toJson(_node.valueType())),
- make_pair("typeDescriptions", typePointerToJson(_node.annotation().type))
+ make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
});
return false;
}
@@ -445,7 +445,7 @@ bool ASTJsonConverter::visit(ArrayTypeName const& _node)
setJsonNode(_node, "ArrayTypeName", {
make_pair("baseType", toJson(_node.baseType())),
make_pair("length", toJsonOrNull(_node.length())),
- make_pair("typeDescriptions", typePointerToJson(_node.annotation().type))
+ make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
});
return false;
}
diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h
index 88b93699..29712f3b 100644
--- a/libsolidity/ast/ASTJsonConverter.h
+++ b/libsolidity/ast/ASTJsonConverter.h
@@ -152,7 +152,7 @@ private:
}
return tmp;
}
- static Json::Value typePointerToJson(TypePointer _tp);
+ static Json::Value typePointerToJson(TypePointer _tp, bool _short = false);
static Json::Value typePointerToJson(std::shared_ptr<std::vector<TypePointer>> _tps);
void appendExpressionAttributes(
std::vector<std::pair<std::string, Json::Value>> &_attributes,
diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h
index 70ee997e..aeff6e4a 100644
--- a/libsolidity/ast/AST_accept.h
+++ b/libsolidity/ast/AST_accept.h
@@ -94,7 +94,8 @@ void InheritanceSpecifier::accept(ASTVisitor& _visitor)
if (_visitor.visit(*this))
{
m_baseName->accept(_visitor);
- listAccept(m_arguments, _visitor);
+ if (m_arguments)
+ listAccept(*m_arguments, _visitor);
}
_visitor.endVisit(*this);
}
@@ -104,7 +105,8 @@ void InheritanceSpecifier::accept(ASTConstVisitor& _visitor) const
if (_visitor.visit(*this))
{
m_baseName->accept(_visitor);
- listAccept(m_arguments, _visitor);
+ if (m_arguments)
+ listAccept(*m_arguments, _visitor);
}
_visitor.endVisit(*this);
}
@@ -262,7 +264,8 @@ void ModifierInvocation::accept(ASTVisitor& _visitor)
if (_visitor.visit(*this))
{
m_modifierName->accept(_visitor);
- listAccept(m_arguments, _visitor);
+ if (m_arguments)
+ listAccept(*m_arguments, _visitor);
}
_visitor.endVisit(*this);
}
@@ -272,7 +275,8 @@ void ModifierInvocation::accept(ASTConstVisitor& _visitor) const
if (_visitor.visit(*this))
{
m_modifierName->accept(_visitor);
- listAccept(m_arguments, _visitor);
+ if (m_arguments)
+ listAccept(*m_arguments, _visitor);
}
_visitor.endVisit(*this);
}
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp
index c08e0e67..51739cb0 100644
--- a/libsolidity/ast/Types.cpp
+++ b/libsolidity/ast/Types.cpp
@@ -28,6 +28,7 @@
#include <libdevcore/CommonData.h>
#include <libdevcore/SHA3.h>
#include <libdevcore/UTF8.h>
+#include <libdevcore/Algorithms.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/replace.hpp>
@@ -43,6 +44,85 @@ using namespace std;
using namespace dev;
using namespace dev::solidity;
+namespace
+{
+
+unsigned int mostSignificantBit(bigint const& _number)
+{
+#if BOOST_VERSION < 105500
+ solAssert(_number > 0, "");
+ bigint number = _number;
+ unsigned int result = 0;
+ while (number != 0)
+ {
+ number >>= 1;
+ ++result;
+ }
+ return --result;
+#else
+ return boost::multiprecision::msb(_number);
+#endif
+}
+
+/// Check whether (_base ** _exp) fits into 4096 bits.
+bool fitsPrecisionExp(bigint const& _base, bigint const& _exp)
+{
+ if (_base == 0)
+ return true;
+
+ solAssert(_base > 0, "");
+
+ size_t const bitsMax = 4096;
+
+ unsigned mostSignificantBaseBit = mostSignificantBit(_base);
+ if (mostSignificantBaseBit == 0) // _base == 1
+ return true;
+ if (mostSignificantBaseBit > bitsMax) // _base >= 2 ^ 4096
+ return false;
+
+ bigint bitsNeeded = _exp * (mostSignificantBaseBit + 1);
+
+ return bitsNeeded <= bitsMax;
+}
+
+/// Checks whether _mantissa * (X ** _exp) fits into 4096 bits,
+/// where X is given indirectly via _log2OfBase = log2(X).
+bool fitsPrecisionBaseX(
+ bigint const& _mantissa,
+ double _log2OfBase,
+ uint32_t _exp
+)
+{
+ if (_mantissa == 0)
+ return true;
+
+ solAssert(_mantissa > 0, "");
+
+ size_t const bitsMax = 4096;
+
+ unsigned mostSignificantMantissaBit = mostSignificantBit(_mantissa);
+ if (mostSignificantMantissaBit > bitsMax) // _mantissa >= 2 ^ 4096
+ return false;
+
+ bigint bitsNeeded = mostSignificantMantissaBit + bigint(floor(double(_exp) * _log2OfBase)) + 1;
+ return bitsNeeded <= bitsMax;
+}
+
+/// Checks whether _mantissa * (10 ** _expBase10) fits into 4096 bits.
+bool fitsPrecisionBase10(bigint const& _mantissa, uint32_t _expBase10)
+{
+ double const log2Of10AwayFromZero = 3.3219280948873624;
+ return fitsPrecisionBaseX(_mantissa, log2Of10AwayFromZero, _expBase10);
+}
+
+/// Checks whether _mantissa * (2 ** _expBase10) fits into 4096 bits.
+bool fitsPrecisionBase2(bigint const& _mantissa, uint32_t _expBase2)
+{
+ return fitsPrecisionBaseX(_mantissa, 1.0, _expBase2);
+}
+
+}
+
void StorageOffsets::computeOffsets(TypePointers const& _types)
{
bigint slotOffset = 0;
@@ -208,9 +288,9 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
case Token::UInt:
return make_shared<IntegerType>(256, IntegerType::Modifier::Unsigned);
case Token::Fixed:
- return make_shared<FixedPointType>(128, 19, FixedPointType::Modifier::Signed);
+ return make_shared<FixedPointType>(128, 18, FixedPointType::Modifier::Signed);
case Token::UFixed:
- return make_shared<FixedPointType>(128, 19, FixedPointType::Modifier::Unsigned);
+ return make_shared<FixedPointType>(128, 18, FixedPointType::Modifier::Unsigned);
case Token::Byte:
return make_shared<FixedBytesType>(1);
case Token::Address:
@@ -232,11 +312,22 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
TypePointer Type::fromElementaryTypeName(string const& _name)
{
+ string name = _name;
+ DataLocation location = DataLocation::Storage;
+ if (boost::algorithm::ends_with(name, " memory"))
+ {
+ name = name.substr(0, name.length() - 7);
+ location = DataLocation::Memory;
+ }
unsigned short firstNum;
unsigned short secondNum;
Token::Value token;
- tie(token, firstNum, secondNum) = Token::fromIdentifierOrKeyword(_name);
- return fromElementaryTypeName(ElementaryTypeNameToken(token, firstNum, secondNum));
+ tie(token, firstNum, secondNum) = Token::fromIdentifierOrKeyword(name);
+ auto t = fromElementaryTypeName(ElementaryTypeNameToken(token, firstNum, secondNum));
+ if (auto* ref = dynamic_cast<ReferenceType const*>(t.get()))
+ return ref->copyForLocation(location, true);
+ else
+ return t;
}
TypePointer Type::forLiteral(Literal const& _literal)
@@ -304,7 +395,7 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition
);
for (FunctionDefinition const* function: library.definedFunctions())
{
- if (!function->isVisibleInDerivedContracts() || seenFunctions.count(function))
+ if (!function->isVisibleAsLibraryMember() || seenFunctions.count(function))
continue;
seenFunctions.insert(function);
FunctionType funType(*function, false);
@@ -327,7 +418,7 @@ bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountT
else if (IntegerType const* otherInt = dynamic_cast<decltype(otherInt)>(&_shiftAmountType))
return !otherInt->isAddress();
else if (RationalNumberType const* otherRat = dynamic_cast<decltype(otherRat)>(&_shiftAmountType))
- return otherRat->integerType() && !otherRat->integerType()->isSigned();
+ return !otherRat->isFractional() && otherRat->integerType() && !otherRat->integerType()->isSigned();
else
return false;
}
@@ -677,31 +768,39 @@ tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal
}
else if (expPoint != _literal.value().end())
{
- // parse the exponent
+ // Parse base and exponent. Checks numeric limit.
bigint exp = bigint(string(expPoint + 1, _literal.value().end()));
if (exp > numeric_limits<int32_t>::max() || exp < numeric_limits<int32_t>::min())
return make_tuple(false, rational(0));
- // parse the base
+ uint32_t expAbs = bigint(abs(exp)).convert_to<uint32_t>();
+
+
tuple<bool, rational> base = parseRational(string(_literal.value().begin(), expPoint));
+
if (!get<0>(base))
return make_tuple(false, rational(0));
value = get<1>(base);
if (exp < 0)
{
- exp *= -1;
+ if (!fitsPrecisionBase10(abs(value.denominator()), expAbs))
+ return make_tuple(false, rational(0));
value /= boost::multiprecision::pow(
bigint(10),
- exp.convert_to<int32_t>()
+ expAbs
);
}
- else
+ else if (exp > 0)
+ {
+ if (!fitsPrecisionBase10(abs(value.numerator()), expAbs))
+ return make_tuple(false, rational(0));
value *= boost::multiprecision::pow(
bigint(10),
- exp.convert_to<int32_t>()
+ expAbs
);
+ }
}
else
{
@@ -827,10 +926,10 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
{
if (_other->category() == Category::Integer || _other->category() == Category::FixedPoint)
{
- auto mobile = mobileType();
- if (!mobile)
+ auto commonType = Type::commonType(shared_from_this(), _other);
+ if (!commonType)
return TypePointer();
- return mobile->binaryOperatorResult(_operator, _other);
+ return commonType->binaryOperatorResult(_operator, _other);
}
else if (_other->category() != category())
return TypePointer();
@@ -900,16 +999,49 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
using boost::multiprecision::pow;
if (other.isFractional())
return TypePointer();
- else if (abs(other.m_value) > numeric_limits<uint32_t>::max())
- return TypePointer(); // This will need too much memory to represent.
- uint32_t exponent = abs(other.m_value).numerator().convert_to<uint32_t>();
- bigint numerator = pow(m_value.numerator(), exponent);
- bigint denominator = pow(m_value.denominator(), exponent);
- if (other.m_value >= 0)
- value = rational(numerator, denominator);
+ solAssert(other.m_value.denominator() == 1, "");
+ bigint const& exp = other.m_value.numerator();
+
+ // x ** 0 = 1
+ // for 0, 1 and -1 the size of the exponent doesn't have to be restricted
+ if (exp == 0)
+ value = 1;
+ else if (m_value.numerator() == 0 || m_value == 1)
+ value = m_value;
+ else if (m_value == -1)
+ {
+ bigint isOdd = abs(exp) & bigint(1);
+ value = 1 - 2 * isOdd.convert_to<int>();
+ }
else
- // invert
- value = rational(denominator, numerator);
+ {
+ if (abs(exp) > numeric_limits<uint32_t>::max())
+ return TypePointer(); // This will need too much memory to represent.
+
+ uint32_t absExp = bigint(abs(exp)).convert_to<uint32_t>();
+
+ // Limit size to 4096 bits
+ if (!fitsPrecisionExp(abs(m_value.numerator()), absExp) || !fitsPrecisionExp(abs(m_value.denominator()), absExp))
+ return TypePointer();
+
+ static auto const optimizedPow = [](bigint const& _base, uint32_t _exponent) -> bigint {
+ if (_base == 1)
+ return 1;
+ else if (_base == -1)
+ return 1 - 2 * int(_exponent & 1);
+ else
+ return pow(_base, _exponent);
+ };
+
+ bigint numerator = optimizedPow(m_value.numerator(), absExp);
+ bigint denominator = optimizedPow(m_value.denominator(), absExp);
+
+ if (exp >= 0)
+ value = rational(numerator, denominator);
+ else
+ // invert
+ value = rational(denominator, numerator);
+ }
break;
}
case Token::SHL:
@@ -921,28 +1053,48 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
return TypePointer();
else if (other.m_value > numeric_limits<uint32_t>::max())
return TypePointer();
- uint32_t exponent = other.m_value.numerator().convert_to<uint32_t>();
- value = m_value.numerator() * pow(bigint(2), exponent);
+ if (m_value.numerator() == 0)
+ value = 0;
+ else
+ {
+ uint32_t exponent = other.m_value.numerator().convert_to<uint32_t>();
+ if (!fitsPrecisionBase2(abs(m_value.numerator()), exponent))
+ return TypePointer();
+ value = m_value.numerator() * pow(bigint(2), exponent);
+ }
break;
}
// NOTE: we're using >> (SAR) to denote right shifting. The type of the LValue
// determines the resulting type and the type of shift (SAR or SHR).
case Token::SAR:
{
- using boost::multiprecision::pow;
+ namespace mp = boost::multiprecision;
if (fractional)
return TypePointer();
else if (other.m_value < 0)
return TypePointer();
else if (other.m_value > numeric_limits<uint32_t>::max())
return TypePointer();
- uint32_t exponent = other.m_value.numerator().convert_to<uint32_t>();
- value = rational(m_value.numerator() / pow(bigint(2), exponent), 1);
+ if (m_value.numerator() == 0)
+ value = 0;
+ else
+ {
+ uint32_t exponent = other.m_value.numerator().convert_to<uint32_t>();
+ if (exponent > mostSignificantBit(mp::abs(m_value.numerator())))
+ value = 0;
+ else
+ value = rational(m_value.numerator() / mp::pow(bigint(2), exponent), 1);
+ }
break;
}
default:
return TypePointer();
}
+
+ // verify that numerator and denominator fit into 4096 bit after every operation
+ if (value.numerator() != 0 && max(mostSignificantBit(abs(value.numerator())), mostSignificantBit(abs(value.denominator()))) > 4096)
+ return TypePointer();
+
return make_shared<RationalNumberType>(value);
}
}
@@ -1262,6 +1414,8 @@ bool ContractType::isPayable() const
TypePointer ContractType::unaryOperatorResult(Token::Value _operator) const
{
+ if (isSuper())
+ return TypePointer{};
return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer();
}
@@ -1969,25 +2123,19 @@ bool StructType::recursive() const
{
if (!m_recursive.is_initialized())
{
- set<StructDefinition const*> structsSeen;
- function<bool(StructType const*)> check = [&](StructType const* t) -> bool
+ auto visitor = [&](StructDefinition const& _struct, CycleDetector<StructDefinition>& _cycleDetector)
{
- StructDefinition const* str = &t->structDefinition();
- if (structsSeen.count(str))
- return true;
- structsSeen.insert(str);
- for (ASTPointer<VariableDeclaration> const& variable: str->members())
+ for (ASTPointer<VariableDeclaration> const& variable: _struct.members())
{
Type const* memberType = variable->annotation().type.get();
while (dynamic_cast<ArrayType const*>(memberType))
memberType = dynamic_cast<ArrayType const*>(memberType)->baseType().get();
if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType))
- if (check(innerStruct))
- return true;
+ if (_cycleDetector.run(innerStruct->structDefinition()))
+ return;
}
- return false;
};
- m_recursive = check(this);
+ m_recursive = (CycleDetector<StructDefinition>(visitor).run(structDefinition()) != nullptr);
}
return *m_recursive;
}
@@ -2311,6 +2459,18 @@ vector<string> FunctionType::parameterNames() const
return vector<string>(m_parameterNames.cbegin() + 1, m_parameterNames.cend());
}
+TypePointers FunctionType::returnParameterTypesWithoutDynamicTypes() const
+{
+ TypePointers returnParameterTypes = m_returnParameterTypes;
+
+ if (m_kind == Kind::External || m_kind == Kind::CallCode || m_kind == Kind::DelegateCall)
+ for (auto& param: returnParameterTypes)
+ if (param->isDynamicallySized() && !param->dataStoredIn(DataLocation::Storage))
+ param = make_shared<InaccessibleDynamicType>();
+
+ return returnParameterTypes;
+}
+
TypePointers FunctionType::parameterTypes() const
{
if (!bound())
@@ -2355,7 +2515,11 @@ string FunctionType::richIdentifier() const
case Kind::ByteArrayPush: id += "bytearraypush"; break;
case Kind::ObjectCreation: id += "objectcreation"; break;
case Kind::Assert: id += "assert"; break;
- case Kind::Require: id += "require";break;
+ case Kind::Require: id += "require"; break;
+ case Kind::ABIEncode: id += "abiencode"; break;
+ case Kind::ABIEncodePacked: id += "abiencodepacked"; break;
+ case Kind::ABIEncodeWithSelector: id += "abiencodewithselector"; break;
+ case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break;
default: solAssert(false, "Unknown function location."); break;
}
id += "_" + stateMutabilityToString(m_stateMutability);
@@ -2772,18 +2936,9 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
kind = Kind::DelegateCall;
}
- TypePointers returnParameterTypes = m_returnParameterTypes;
- if (kind != Kind::Internal)
- {
- // Alter dynamic types to be non-accessible.
- for (auto& param: returnParameterTypes)
- if (param->isDynamicallySized())
- param = make_shared<InaccessibleDynamicType>();
- }
-
return make_shared<FunctionType>(
parameterTypes,
- returnParameterTypes,
+ m_returnParameterTypes,
m_parameterNames,
m_returnParameterNames,
kind,
@@ -2875,7 +3030,7 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current
}
if (contract.isLibrary())
for (FunctionDefinition const* function: contract.definedFunctions())
- if (function->isVisibleInDerivedContracts())
+ if (function->isVisibleAsLibraryMember())
members.push_back(MemberList::Member(
function->name(),
FunctionType(*function).asMemberFunction(true),
@@ -2985,6 +3140,8 @@ string MagicType::richIdentifier() const
return "t_magic_message";
case Kind::Transaction:
return "t_magic_transaction";
+ case Kind::ABI:
+ return "t_magic_abi";
default:
solAssert(false, "Unknown kind of magic");
}
@@ -3025,6 +3182,45 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
{"origin", make_shared<IntegerType>(160, IntegerType::Modifier::Address)},
{"gasprice", make_shared<IntegerType>(256)}
});
+ case Kind::ABI:
+ return MemberList::MemberMap({
+ {"encode", make_shared<FunctionType>(
+ TypePointers(),
+ TypePointers{make_shared<ArrayType>(DataLocation::Memory)},
+ strings{},
+ strings{},
+ FunctionType::Kind::ABIEncode,
+ true,
+ StateMutability::Pure
+ )},
+ {"encodePacked", make_shared<FunctionType>(
+ TypePointers(),
+ TypePointers{make_shared<ArrayType>(DataLocation::Memory)},
+ strings{},
+ strings{},
+ FunctionType::Kind::ABIEncodePacked,
+ true,
+ StateMutability::Pure
+ )},
+ {"encodeWithSelector", make_shared<FunctionType>(
+ TypePointers{make_shared<FixedBytesType>(4)},
+ TypePointers{make_shared<ArrayType>(DataLocation::Memory)},
+ strings{},
+ strings{},
+ FunctionType::Kind::ABIEncodeWithSelector,
+ true,
+ StateMutability::Pure
+ )},
+ {"encodeWithSignature", make_shared<FunctionType>(
+ TypePointers{make_shared<ArrayType>(DataLocation::Memory, true)},
+ TypePointers{make_shared<ArrayType>(DataLocation::Memory)},
+ strings{},
+ strings{},
+ FunctionType::Kind::ABIEncodeWithSignature,
+ true,
+ StateMutability::Pure
+ )}
+ });
default:
solAssert(false, "Unknown kind of magic.");
}
@@ -3040,6 +3236,8 @@ string MagicType::toString(bool) const
return "msg";
case Kind::Transaction:
return "tx";
+ case Kind::ABI:
+ return "abi";
default:
solAssert(false, "Unknown kind of magic.");
}
diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h
index c20a025f..345f84a1 100644
--- a/libsolidity/ast/Types.h
+++ b/libsolidity/ast/Types.h
@@ -150,6 +150,7 @@ public:
/// @name Factory functions
/// Factory functions that convert an AST @ref TypeName to a Type.
static TypePointer fromElementaryTypeName(ElementaryTypeNameToken const& _type);
+ /// Converts a given elementary type name with optional suffix " memory" to a type pointer.
static TypePointer fromElementaryTypeName(std::string const& _name);
/// @}
@@ -229,6 +230,9 @@ public:
/// i.e. it behaves differently in lvalue context and in value context.
virtual bool isValueType() const { return false; }
virtual unsigned sizeOnStack() const { return 1; }
+ /// If it is possible to initialize such a value in memory by just writing zeros
+ /// of the size memoryHeadSize().
+ virtual bool hasSimpleZeroValueInMemory() const { return true; }
/// @returns the mobile (in contrast to static) type corresponding to the given type.
/// This returns the corresponding IntegerType or FixedPointType for RationalNumberType
/// and the pointer type for storage reference types.
@@ -399,7 +403,7 @@ private:
};
/**
- * Integer and fixed point constants either literals or computed.
+ * Integer and fixed point constants either literals or computed.
* Example expressions: 2, 3.14, 2+10.2, ~10.
* There is one distinct type per value.
*/
@@ -411,7 +415,7 @@ public:
/// @returns true if the literal is a valid integer.
static std::tuple<bool, rational> isValidLiteral(Literal const& _literal);
-
+
explicit RationalNumberType(rational const& _value):
m_value(_value)
{}
@@ -432,7 +436,7 @@ public:
/// @returns the smallest integer type that can hold the value or an empty pointer if not possible.
std::shared_ptr<IntegerType const> integerType() const;
- /// @returns the smallest fixed type that can hold the value or incurs the least precision loss.
+ /// @returns the smallest fixed type that can hold the value or incurs the least precision loss.
/// If the integer part does not fit, returns an empty pointer.
std::shared_ptr<FixedPointType const> fixedPointType() const;
@@ -442,6 +446,9 @@ public:
/// @returns true if the value is negative.
bool isNegative() const { return m_value < 0; }
+ /// @returns true if the value is zero.
+ bool isZero() const { return m_value == 0; }
+
private:
rational m_value;
@@ -568,6 +575,7 @@ public:
virtual TypePointer mobileType() const override { return copyForLocation(m_location, true); }
virtual bool dataStoredIn(DataLocation _location) const override { return m_location == _location; }
+ virtual bool hasSimpleZeroValueInMemory() const override { return false; }
/// Storage references can be pointers or bound references. In general, local variables are of
/// pointer type, state variables are bound references. Assignments to pointers or deleting
@@ -692,22 +700,27 @@ public:
virtual bool operator==(Type const& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded ) const override
{
+ solAssert(!isSuper(), "");
return encodingType()->calldataEncodedSize(_padded);
}
- virtual unsigned storageBytes() const override { return 20; }
- virtual bool canLiveOutsideStorage() const override { return true; }
+ virtual unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; }
+ virtual bool canLiveOutsideStorage() const override { return !isSuper(); }
virtual unsigned sizeOnStack() const override { return m_super ? 0 : 1; }
- virtual bool isValueType() const override { return true; }
+ virtual bool isValueType() const override { return !isSuper(); }
virtual std::string toString(bool _short) const override;
virtual std::string canonicalName() const override;
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
virtual TypePointer encodingType() const override
{
+ if (isSuper())
+ return TypePointer{};
return std::make_shared<IntegerType>(160, IntegerType::Modifier::Address);
}
virtual TypePointer interfaceType(bool _inLibrary) const override
{
+ if (isSuper())
+ return TypePointer{};
return _inLibrary ? shared_from_this() : encodingType();
}
@@ -768,7 +781,7 @@ public:
virtual std::string canonicalName() const override;
virtual std::string signatureInExternalFunction(bool _structsByName) const override;
- /// @returns a function that peforms the type conversion between a list of struct members
+ /// @returns a function that performs the type conversion between a list of struct members
/// and a memory struct of this type.
FunctionTypePointer constructorType() const;
@@ -850,6 +863,7 @@ public:
virtual u256 storageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned sizeOnStack() const override;
+ virtual bool hasSimpleZeroValueInMemory() const override { return false; }
virtual TypePointer mobileType() const override;
/// Converts components to their temporary types and performs some wildcard matching.
virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const override;
@@ -903,6 +917,10 @@ public:
ObjectCreation, ///< array creation using new
Assert, ///< assert()
Require, ///< require()
+ ABIEncode,
+ ABIEncodePacked,
+ ABIEncodeWithSelector,
+ ABIEncodeWithSignature,
GasLeft ///< gasleft()
};
@@ -973,6 +991,9 @@ public:
TypePointers parameterTypes() const;
std::vector<std::string> parameterNames() const;
TypePointers const& returnParameterTypes() const { return m_returnParameterTypes; }
+ /// @returns the list of return parameter types. All dynamically-sized types (this excludes
+ /// storage pointers) are replaced by InaccessibleDynamicType instances.
+ TypePointers returnParameterTypesWithoutDynamicTypes() const;
std::vector<std::string> const& returnParameterNames() const { return m_returnParameterNames; }
/// @returns the "self" parameter type for a bound function
TypePointer const& selfType() const;
@@ -991,6 +1012,7 @@ public:
virtual bool isValueType() const override { return true; }
virtual bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
virtual unsigned sizeOnStack() const override;
+ virtual bool hasSimpleZeroValueInMemory() const override { return false; }
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
virtual TypePointer encodingType() const override;
virtual TypePointer interfaceType(bool _inLibrary) const override;
@@ -1024,7 +1046,7 @@ public:
return *m_declaration;
}
bool hasDeclaration() const { return !!m_declaration; }
- /// @returns true if the the result of this function only depends on its arguments
+ /// @returns true if 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;
@@ -1034,14 +1056,14 @@ public:
ASTPointer<ASTString> documentation() const;
/// true iff arguments are to be padded to multiples of 32 bytes for external calls
- bool padArguments() const { return !(m_kind == Kind::SHA3 || m_kind == Kind::SHA256 || m_kind == Kind::RIPEMD160); }
+ bool padArguments() const { return !(m_kind == Kind::SHA3 || m_kind == Kind::SHA256 || m_kind == Kind::RIPEMD160 || m_kind == Kind::ABIEncodePacked); }
bool takesArbitraryParameters() const { return m_arbitraryParameters; }
bool gasSet() const { return m_gasSet; }
bool valueSet() const { return m_valueSet; }
bool bound() const { return m_bound; }
/// @returns a copy of this type, where gas or value are set manually. This will never set one
- /// of the parameters to fals.
+ /// of the parameters to false.
TypePointer copyAndSetGasOrValue(bool _setGas, bool _setValue) const;
/// @returns a copy of this function type where all return parameters of dynamic size are
@@ -1096,6 +1118,8 @@ public:
return _inLibrary ? shared_from_this() : TypePointer();
}
virtual bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; }
+ /// Cannot be stored in memory, but just in case.
+ virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
TypePointer const& keyType() const { return m_keyType; }
TypePointer const& valueType() const { return m_valueType; }
@@ -1124,6 +1148,7 @@ public:
virtual u256 storageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned sizeOnStack() const override;
+ virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
virtual std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; }
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
@@ -1146,6 +1171,7 @@ public:
virtual u256 storageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned sizeOnStack() const override { return 0; }
+ virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
virtual std::string richIdentifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual std::string toString(bool _short) const override;
@@ -1171,6 +1197,7 @@ public:
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
virtual bool canLiveOutsideStorage() const override { return true; }
+ virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
virtual unsigned sizeOnStack() const override { return 0; }
virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
@@ -1187,7 +1214,7 @@ private:
class MagicType: public Type
{
public:
- enum class Kind { Block, Message, Transaction };
+ enum class Kind { Block, Message, Transaction, ABI };
virtual Category category() const override { return Category::Magic; }
explicit MagicType(Kind _kind): m_kind(_kind) {}
@@ -1201,6 +1228,7 @@ public:
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
virtual bool canLiveOutsideStorage() const override { return true; }
+ virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
virtual unsigned sizeOnStack() const override { return 0; }
virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
@@ -1230,6 +1258,7 @@ public:
virtual bool canLiveOutsideStorage() const override { return false; }
virtual bool isValueType() const override { return true; }
virtual unsigned sizeOnStack() const override { return 1; }
+ virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
virtual std::string toString(bool) const override { return "inaccessible dynamic type"; }
virtual TypePointer decodingType() const override { return std::make_shared<IntegerType>(256); }
};
diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp
index 00f59065..8e890854 100644
--- a/libsolidity/codegen/ABIFunctions.cpp
+++ b/libsolidity/codegen/ABIFunctions.cpp
@@ -253,6 +253,9 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
templ("body", w.render());
break;
}
+ case Type::Category::InaccessibleDynamic:
+ templ("body", "cleaned := 0");
+ break;
default:
solAssert(false, "Cleanup of type " + _type.identifier() + " requested.");
}
diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp
index ce8cbb5f..0fe66d2d 100644
--- a/libsolidity/codegen/ArrayUtils.cpp
+++ b/libsolidity/codegen/ArrayUtils.cpp
@@ -741,10 +741,10 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const
if (_type.isByteArray())
// For a "long" byte array, store length as 2*length+1
_context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD;
- _context<< Instruction::DUP4 << Instruction::SSTORE;
+ _context << Instruction::DUP4 << Instruction::SSTORE;
// skip if size is not reduced
_context << Instruction::DUP2 << Instruction::DUP2
- << Instruction::ISZERO << Instruction::GT;
+ << Instruction::GT << Instruction::ISZERO;
_context.appendConditionalJumpTo(resizeEnd);
// size reduced, clear the end of the array
@@ -774,6 +774,55 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const
);
}
+void ArrayUtils::incrementDynamicArraySize(ArrayType const& _type) const
+{
+ solAssert(_type.location() == DataLocation::Storage, "");
+ solAssert(_type.isDynamicallySized(), "");
+ if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32)
+ solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
+
+ if (_type.isByteArray())
+ {
+ // We almost always just add 2 (length of byte arrays is shifted left by one)
+ // except for the case where we transition from a short byte array
+ // to a long byte array, there we have to copy.
+ // This happens if the length is exactly 31, which means that the
+ // lowest-order byte (we actually use a mask with fewer bits) must
+ // be (31*2+0) = 62
+
+ m_context.appendInlineAssembly(R"({
+ let data := sload(ref)
+ let shifted_length := and(data, 63)
+ // We have to copy if length is exactly 31, because that marks
+ // the transition between in-place and out-of-place storage.
+ switch shifted_length
+ case 62
+ {
+ mstore(0, ref)
+ let data_area := keccak256(0, 0x20)
+ sstore(data_area, and(data, not(0xff)))
+ // New length is 32, encoded as (32 * 2 + 1)
+ sstore(ref, 65)
+ // Replace ref variable by new length
+ ref := 32
+ }
+ default
+ {
+ sstore(ref, add(data, 2))
+ // Replace ref variable by new length
+ if iszero(and(data, 1)) { data := shifted_length }
+ ref := add(div(data, 2), 1)
+ }
+ })", {"ref"});
+ }
+ else
+ m_context.appendInlineAssembly(R"({
+ let new_length := add(sload(ref), 1)
+ sstore(ref, new_length)
+ ref := new_length
+ })", {"ref"});
+}
+
void ArrayUtils::clearStorageLoop(TypePointer const& _type) const
{
m_context.callLowLevelFunction(
diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h
index f3ddc4ee..99786397 100644
--- a/libsolidity/codegen/ArrayUtils.h
+++ b/libsolidity/codegen/ArrayUtils.h
@@ -67,6 +67,12 @@ public:
/// Stack pre: reference (excludes byte offset) new_length
/// Stack post:
void resizeDynamicArray(ArrayType const& _type) const;
+ /// Increments the size of a dynamic array by one.
+ /// Does not touch the new data element. In case of a byte array, this might move the
+ /// data.
+ /// Stack pre: reference (excludes byte offset)
+ /// Stack post: new_length
+ void incrementDynamicArraySize(ArrayType const& _type) const;
/// Appends a loop that clears a sequence of storage slots of the given type (excluding end).
/// Stack pre: end_ref start_ref
/// Stack post: end_ref
diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp
index 0bf88267..a35eea73 100644
--- a/libsolidity/codegen/CompilerContext.cpp
+++ b/libsolidity/codegen/CompilerContext.cpp
@@ -193,14 +193,22 @@ Declaration const* CompilerContext::nextFunctionToCompile() const
return m_functionCompilationQueue.nextFunctionToCompile();
}
-ModifierDefinition const& CompilerContext::functionModifier(string const& _name) const
+ModifierDefinition const& CompilerContext::resolveVirtualFunctionModifier(
+ ModifierDefinition const& _modifier
+) const
{
+ // Libraries do not allow inheritance and their functions can be inlined, so we should not
+ // search the inheritance hierarchy (which will be the wrong one in case the function
+ // is inlined).
+ if (auto scope = dynamic_cast<ContractDefinition const*>(_modifier.scope()))
+ if (scope->isLibrary())
+ return _modifier;
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
for (ContractDefinition const* contract: m_inheritanceHierarchy)
for (ModifierDefinition const* modifier: contract->functionModifiers())
- if (modifier->name() == _name)
+ if (modifier->name() == _modifier.name())
return *modifier;
- solAssert(false, "Function modifier " + _name + " not found.");
+ solAssert(false, "Function modifier " + _modifier.name() + " not found in inheritance hierarchy.");
}
unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declaration) const
@@ -254,12 +262,20 @@ CompilerContext& CompilerContext::appendRevert()
return *this << u256(0) << u256(0) << Instruction::REVERT;
}
-CompilerContext& CompilerContext::appendConditionalRevert()
+CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnData)
{
- *this << Instruction::ISZERO;
- eth::AssemblyItem afterTag = appendConditionalJump();
- appendRevert();
- *this << afterTag;
+ if (_forwardReturnData && m_evmVersion.supportsReturndata())
+ appendInlineAssembly(R"({
+ if condition {
+ returndatacopy(0, 0, returndatasize())
+ revert(0, returndatasize())
+ }
+ })", {"condition"});
+ else
+ appendInlineAssembly(R"({
+ if condition { revert(0, 0) }
+ })", {"condition"});
+ *this << Instruction::POP;
return *this;
}
diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h
index cf626683..098472f7 100644
--- a/libsolidity/codegen/CompilerContext.h
+++ b/libsolidity/codegen/CompilerContext.h
@@ -130,7 +130,7 @@ public:
void appendMissingLowLevelFunctions();
ABIFunctions& abiFunctions() { return m_abiFunctions; }
- ModifierDefinition const& functionModifier(std::string const& _name) const;
+ ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const;
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const;
/// If supplied by a value returned by @ref baseStackOffsetOfVariable(variable), returns
@@ -156,8 +156,11 @@ public:
CompilerContext& appendConditionalInvalid();
/// Appends a REVERT(0, 0) call
CompilerContext& appendRevert();
- /// Appends a conditional REVERT(0, 0) call
- CompilerContext& appendConditionalRevert();
+ /// Appends a conditional REVERT-call, either forwarding the RETURNDATA or providing the
+ /// empty string. Consumes the condition.
+ /// If the current EVM version does not support RETURNDATA, uses REVERT but does not forward
+ /// the data.
+ CompilerContext& appendConditionalRevert(bool _forwardReturnData = false);
/// Appends a JUMP to a specific tag
CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; }
/// Appends pushing of a new tag and @returns the new tag.
diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp
index 533aca5c..4af7d905 100644
--- a/libsolidity/codegen/CompilerUtils.cpp
+++ b/libsolidity/codegen/CompilerUtils.cpp
@@ -21,12 +21,16 @@
*/
#include <libsolidity/codegen/CompilerUtils.h>
+
#include <libsolidity/ast/AST.h>
-#include <libevmasm/Instruction.h>
#include <libsolidity/codegen/ArrayUtils.h>
#include <libsolidity/codegen/LValue.h>
#include <libsolidity/codegen/ABIFunctions.h>
+#include <libevmasm/Instruction.h>
+
+#include <libdevcore/Whiskers.h>
+
using namespace std;
namespace dev
@@ -36,11 +40,17 @@ namespace solidity
const unsigned CompilerUtils::dataStartOffset = 4;
const size_t CompilerUtils::freeMemoryPointer = 64;
+const size_t CompilerUtils::zeroPointer = CompilerUtils::freeMemoryPointer + 32;
+const size_t CompilerUtils::generalPurposeMemoryStart = CompilerUtils::zeroPointer + 32;
const unsigned CompilerUtils::identityContractAddress = 4;
+static_assert(CompilerUtils::freeMemoryPointer >= 64, "Free memory pointer must not overlap with scratch area.");
+static_assert(CompilerUtils::zeroPointer >= CompilerUtils::freeMemoryPointer + 32, "Zero pointer must not overlap with free memory pointer.");
+static_assert(CompilerUtils::generalPurposeMemoryStart >= CompilerUtils::zeroPointer + 32, "General purpose memory must not overlap with zero area.");
+
void CompilerUtils::initialiseFreeMemoryPointer()
{
- m_context << u256(freeMemoryPointer + 32);
+ m_context << u256(generalPurposeMemoryStart);
storeFreeMemoryPointer();
}
@@ -68,6 +78,20 @@ void CompilerUtils::toSizeAfterFreeMemoryPointer()
m_context << Instruction::SWAP1;
}
+void CompilerUtils::revertWithStringData(Type const& _argumentType)
+{
+ solAssert(_argumentType.isImplicitlyConvertibleTo(*Type::fromElementaryTypeName("string memory")), "");
+ fetchFreeMemoryPointer();
+ m_context << (u256(FixedHash<4>::Arith(FixedHash<4>(dev::keccak256("Error(string)")))) << (256 - 32));
+ m_context << Instruction::DUP2 << Instruction::MSTORE;
+ m_context << u256(4) << Instruction::ADD;
+ // Stack: <string data> <mem pos of encoding start>
+ abiEncode({_argumentType.shared_from_this()}, {make_shared<ArrayType>(DataLocation::Memory, true)});
+ toSizeAfterFreeMemoryPointer();
+ m_context << Instruction::REVERT;
+ m_context.adjustStackOffset(_argumentType.sizeOnStack());
+}
+
unsigned CompilerUtils::loadFromMemory(
unsigned _offset,
Type const& _type,
@@ -139,7 +163,6 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External
)
{
- solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented.");
combineExternalFunctionType(true);
m_context << Instruction::DUP2 << Instruction::MSTORE;
m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD;
@@ -159,6 +182,163 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
}
}
+void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMemory, bool _revertOnOutOfBounds)
+{
+ /// Stack: <source_offset> <length>
+ if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
+ {
+ // Use the new JULIA-based decoding function
+ auto stackHeightBefore = m_context.stackHeight();
+ abiDecodeV2(_typeParameters, _fromMemory);
+ solAssert(m_context.stackHeight() - stackHeightBefore == sizeOnStack(_typeParameters) - 2, "");
+ return;
+ }
+
+ //@todo this does not yet support nested dynamic arrays
+
+ if (_revertOnOutOfBounds)
+ {
+ size_t encodedSize = 0;
+ for (auto const& t: _typeParameters)
+ encodedSize += t->decodingType()->calldataEncodedSize(true);
+ m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"});
+ }
+
+ m_context << Instruction::DUP2 << Instruction::ADD;
+ m_context << Instruction::SWAP1;
+ /// Stack: <input_end> <source_offset>
+
+ // Retain the offset pointer as base_offset, the point from which the data offsets are computed.
+ m_context << Instruction::DUP1;
+ for (TypePointer const& parameterType: _typeParameters)
+ {
+ // stack: v1 v2 ... v(k-1) input_end base_offset current_offset
+ TypePointer type = parameterType->decodingType();
+ solUnimplementedAssert(type, "No decoding type found.");
+ if (type->category() == Type::Category::Array)
+ {
+ auto const& arrayType = dynamic_cast<ArrayType const&>(*type);
+ solUnimplementedAssert(!arrayType.baseType()->isDynamicallyEncoded(), "Nested arrays not yet implemented.");
+ if (_fromMemory)
+ {
+ solUnimplementedAssert(
+ arrayType.baseType()->isValueType(),
+ "Nested memory arrays not yet implemented here."
+ );
+ // @todo If base type is an array or struct, it is still calldata-style encoded, so
+ // we would have to convert it like below.
+ solAssert(arrayType.location() == DataLocation::Memory, "");
+ if (arrayType.isDynamicallySized())
+ {
+ // compute data pointer
+ m_context << Instruction::DUP1 << Instruction::MLOAD;
+ if (_revertOnOutOfBounds)
+ {
+ // Check that the data pointer is valid and that length times
+ // item size is still inside the range.
+ Whiskers templ(R"({
+ if gt(ptr, 0x100000000) { revert(0, 0) }
+ ptr := add(ptr, base_offset)
+ let array_data_start := add(ptr, 0x20)
+ if gt(array_data_start, input_end) { revert(0, 0) }
+ let array_length := mload(ptr)
+ if or(
+ gt(array_length, 0x100000000),
+ gt(add(array_data_start, mul(array_length, <item_size>)), input_end)
+ ) { revert(0, 0) }
+ })");
+ templ("item_size", to_string(arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true)));
+ m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr"});
+ }
+ else
+ m_context << Instruction::DUP3 << Instruction::ADD;
+ // stack: v1 v2 ... v(k-1) input_end base_offset current_offset v(k)
+ moveIntoStack(3);
+ m_context << u256(0x20) << Instruction::ADD;
+ }
+ else
+ {
+ // Size has already been checked for this one.
+ moveIntoStack(2);
+ m_context << Instruction::DUP3;
+ m_context << u256(arrayType.calldataEncodedSize(true)) << Instruction::ADD;
+ }
+ }
+ else
+ {
+ // first load from calldata and potentially convert to memory if arrayType is memory
+ TypePointer calldataType = arrayType.copyForLocation(DataLocation::CallData, false);
+ if (calldataType->isDynamicallySized())
+ {
+ // put on stack: data_pointer length
+ loadFromMemoryDynamic(IntegerType(256), !_fromMemory);
+ m_context << Instruction::SWAP1;
+ // stack: input_end base_offset next_pointer data_offset
+ if (_revertOnOutOfBounds)
+ m_context.appendInlineAssembly("{ if gt(data_offset, 0x100000000) { revert(0, 0) } }", {"data_offset"});
+ m_context << Instruction::DUP3 << Instruction::ADD;
+ // stack: input_end base_offset next_pointer array_head_ptr
+ if (_revertOnOutOfBounds)
+ m_context.appendInlineAssembly(
+ "{ if gt(add(array_head_ptr, 0x20), input_end) { revert(0, 0) } }",
+ {"input_end", "base_offset", "next_ptr", "array_head_ptr"}
+ );
+ // retrieve length
+ loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true);
+ // stack: input_end base_offset next_pointer array_length data_pointer
+ m_context << Instruction::SWAP2;
+ // stack: input_end base_offset data_pointer array_length next_pointer
+ if (_revertOnOutOfBounds)
+ {
+ unsigned itemSize = arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true);
+ m_context.appendInlineAssembly(R"({
+ if or(
+ gt(array_length, 0x100000000),
+ gt(add(data_ptr, mul(array_length, )" + to_string(itemSize) + R"()), input_end)
+ ) { revert(0, 0) }
+ })", {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"});
+ }
+ }
+ else
+ {
+ // size has already been checked
+ // stack: input_end base_offset data_offset
+ m_context << Instruction::DUP1;
+ m_context << u256(calldataType->calldataEncodedSize()) << Instruction::ADD;
+ }
+ if (arrayType.location() == DataLocation::Memory)
+ {
+ // stack: input_end base_offset calldata_ref [length] next_calldata
+ // copy to memory
+ // move calldata type up again
+ moveIntoStack(calldataType->sizeOnStack());
+ convertType(*calldataType, arrayType, false, false, true);
+ // fetch next pointer again
+ moveToStackTop(arrayType.sizeOnStack());
+ }
+ // move input_end up
+ // stack: input_end base_offset calldata_ref [length] next_calldata
+ moveToStackTop(2 + arrayType.sizeOnStack());
+ m_context << Instruction::SWAP1;
+ // stack: base_offset calldata_ref [length] input_end next_calldata
+ moveToStackTop(2 + arrayType.sizeOnStack());
+ m_context << Instruction::SWAP1;
+ // stack: calldata_ref [length] input_end base_offset next_calldata
+ }
+ }
+ else
+ {
+ solAssert(!type->isDynamicallyEncoded(), "Unknown dynamically sized type: " + type->toString());
+ loadFromMemoryDynamic(*type, !_fromMemory, true);
+ // stack: v1 v2 ... v(k-1) input_end base_offset v(k) mem_offset
+ moveToStackTop(1, type->sizeOnStack());
+ moveIntoStack(3, type->sizeOnStack());
+ }
+ // stack: v1 v2 ... v(k-1) v(k) input_end base_offset next_offset
+ }
+ popStackSlots(3);
+}
+
void CompilerUtils::encodeToMemory(
TypePointers const& _givenTypes,
TypePointers const& _targetTypes,
@@ -321,15 +501,13 @@ void CompilerUtils::abiEncodeV2(
void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory)
{
- // stack: <source_offset>
+ // stack: <source_offset> <length> [stack top]
auto ret = m_context.pushNewTag();
+ moveIntoStack(2);
+ // stack: <return tag> <source_offset> <length> [stack top]
+ m_context << Instruction::DUP2 << Instruction::ADD;
m_context << Instruction::SWAP1;
- if (_fromMemory)
- // TODO pass correct size for the memory case
- m_context << (u256(1) << 63);
- else
- m_context << Instruction::CALLDATASIZE;
- m_context << Instruction::SWAP1;
+ // stack: <return tag> <end> <start>
string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory);
m_context.appendJumpTo(m_context.namedTag(decoderName));
m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3);
@@ -338,14 +516,34 @@ void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromM
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
{
- auto repeat = m_context.newTag();
- m_context << repeat;
- pushZeroValue(*_type.baseType());
- storeInMemoryDynamic(*_type.baseType());
- m_context << Instruction::SWAP1 << u256(1) << Instruction::SWAP1;
- m_context << Instruction::SUB << Instruction::SWAP1;
- m_context << Instruction::DUP2;
- m_context.appendConditionalJumpTo(repeat);
+ if (_type.baseType()->hasSimpleZeroValueInMemory())
+ {
+ solAssert(_type.baseType()->isValueType(), "");
+ Whiskers templ(R"({
+ let size := mul(length, <element_size>)
+ // cheap way of zero-initializing a memory range
+ codecopy(memptr, codesize(), size)
+ memptr := add(memptr, size)
+ })");
+ templ("element_size", to_string(_type.baseType()->memoryHeadSize()));
+ m_context.appendInlineAssembly(templ.render(), {"length", "memptr"});
+ }
+ else
+ {
+ // TODO: Potential optimization:
+ // When we create a new multi-dimensional dynamic array, each element
+ // is initialized to an empty array. It actually does not hurt
+ // to re-use exactly the same empty array for all elements. Currently,
+ // a new one is created each time.
+ auto repeat = m_context.newTag();
+ m_context << repeat;
+ pushZeroValue(*_type.baseType());
+ storeInMemoryDynamic(*_type.baseType());
+ m_context << Instruction::SWAP1 << u256(1) << Instruction::SWAP1;
+ m_context << Instruction::SUB << Instruction::SWAP1;
+ m_context << Instruction::DUP2;
+ m_context.appendConditionalJumpTo(repeat);
+ }
m_context << Instruction::SWAP1 << Instruction::POP;
}
@@ -427,7 +625,7 @@ void CompilerUtils::combineExternalFunctionType(bool _leftAligned)
leftShiftNumberOnStack(64);
}
-void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function)
+void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function, bool _runtimeOnly)
{
m_context << m_context.functionEntryLabel(_function).pushTag();
// If there is a runtime context, we have to merge both labels into the same
@@ -435,9 +633,10 @@ void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function)
if (CompilerContext* rtc = m_context.runtimeContext())
{
leftShiftNumberOnStack(32);
- m_context <<
- rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) <<
- Instruction::OR;
+ if (_runtimeOnly)
+ m_context <<
+ rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) <<
+ Instruction::OR;
}
}
@@ -485,19 +684,17 @@ void CompilerUtils::convertType(
// clear for conversion to longer bytes
solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
FixedBytesType const& targetType = dynamic_cast<FixedBytesType const&>(_targetType);
- if (targetType.numBytes() > typeOnStack.numBytes() || _cleanupNeeded)
+ if (typeOnStack.numBytes() == 0 || targetType.numBytes() == 0)
+ m_context << Instruction::POP << u256(0);
+ else if (targetType.numBytes() > typeOnStack.numBytes() || _cleanupNeeded)
{
- if (typeOnStack.numBytes() == 0)
- m_context << Instruction::POP << u256(0);
- else
- {
- m_context << ((u256(1) << (256 - typeOnStack.numBytes() * 8)) - 1);
- m_context << Instruction::NOT << Instruction::AND;
- }
+ int bytes = min(typeOnStack.numBytes(), targetType.numBytes());
+ m_context << ((u256(1) << (256 - bytes * 8)) - 1);
+ m_context << Instruction::NOT << Instruction::AND;
}
}
- }
break;
+ }
case Type::Category::Enum:
solAssert(_targetType == _typeOnStack || targetTypeCategory == Type::Category::Integer, "");
if (enumOverflowCheckPending)
@@ -506,6 +703,7 @@ void CompilerUtils::convertType(
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
if (_asPartOfArgumentDecoding)
+ // TODO: error message?
m_context.appendConditionalRevert();
else
m_context.appendConditionalInvalid();
@@ -598,8 +796,9 @@ void CompilerUtils::convertType(
bytesConstRef data(value);
if (targetTypeCategory == Type::Category::FixedBytes)
{
+ int const numBytes = dynamic_cast<FixedBytesType const&>(_targetType).numBytes();
solAssert(data.size() <= 32, "");
- m_context << h256::Arith(h256(data, h256::AlignLeft));
+ m_context << (h256::Arith(h256(data, h256::AlignLeft)) & (~(u256(-1) >> (8 * numBytes))));
}
else if (targetTypeCategory == Type::Category::Array)
{
@@ -873,6 +1072,13 @@ void CompilerUtils::pushZeroValue(Type const& _type)
return;
}
solAssert(referenceType->location() == DataLocation::Memory, "");
+ if (auto arrayType = dynamic_cast<ArrayType const*>(&_type))
+ if (arrayType->isDynamicallySized())
+ {
+ // Push a memory location that is (hopefully) always zero.
+ pushZeroPointer();
+ return;
+ }
TypePointer type = _type.shared_from_this();
m_context.callLowLevelFunction(
@@ -893,13 +1099,8 @@ void CompilerUtils::pushZeroValue(Type const& _type)
}
else if (auto arrayType = dynamic_cast<ArrayType const*>(type.get()))
{
- if (arrayType->isDynamicallySized())
- {
- // zero length
- _context << u256(0);
- utils.storeInMemoryDynamic(IntegerType(256));
- }
- else if (arrayType->length() > 0)
+ solAssert(!arrayType->isDynamicallySized(), "");
+ if (arrayType->length() > 0)
{
_context << arrayType->length() << Instruction::SWAP1;
// stack: items_to_do memory_pos
@@ -916,6 +1117,11 @@ void CompilerUtils::pushZeroValue(Type const& _type)
);
}
+void CompilerUtils::pushZeroPointer()
+{
+ m_context << u256(zeroPointer);
+}
+
void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
{
unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.baseStackOffsetOfVariable(_variable));
diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h
index 3cde281b..476a7559 100644
--- a/libsolidity/codegen/CompilerUtils.h
+++ b/libsolidity/codegen/CompilerUtils.h
@@ -54,6 +54,13 @@ public:
/// Stack post: <size> <mem_start>
void toSizeAfterFreeMemoryPointer();
+ /// Appends code that performs a revert, providing the given string data.
+ /// Will also append an error signature corresponding to Error(string).
+ /// @param _argumentType the type of the string argument, will be converted to memory string.
+ /// Stack pre: string data
+ /// Stack post:
+ void revertWithStringData(Type const& _argumentType);
+
/// Loads data from memory to the stack.
/// @param _offset offset in memory (or calldata)
/// @param _type data type to load
@@ -88,6 +95,15 @@ public:
/// Stack post: (memory_offset+length)
void storeInMemoryDynamic(Type const& _type, bool _padToWords = true);
+ /// Creates code that unpacks the arguments according to their types specified by a vector of TypePointers.
+ /// From memory if @a _fromMemory is true, otherwise from call data.
+ /// Calls revert if @a _revertOnOutOfBounds is true and the supplied size is shorter
+ /// than the static data requirements or if dynamic data pointers reach outside of the
+ /// area. Also has a hard cap of 0x100000000 for any given length/offset field.
+ /// Stack pre: <source_offset> <length>
+ /// Stack post: <value0> <value1> ... <valuen>
+ void abiDecode(TypePointers const& _typeParameters, bool _fromMemory = false, bool _revertOnOutOfBounds = false);
+
/// Copies values (of types @a _givenTypes) given on the stack to a location in memory given
/// at the stack top, encoding them according to the ABI as the given types @a _targetTypes.
/// Removes the values from the stack and leaves the updated memory pointer.
@@ -149,7 +165,7 @@ public:
/// Decodes data from ABI encoding into internal encoding. If @a _fromMemory is set to true,
/// the data is taken from memory instead of from calldata.
/// Can allocate memory.
- /// Stack pre: <source_offset>
+ /// Stack pre: <source_offset> <length>
/// Stack post: <value0> <value1> ... <valuen>
void abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory = false);
@@ -179,7 +195,8 @@ public:
/// Appends code that combines the construction-time (if available) and runtime function
/// entry label of the given function into a single stack slot.
/// Note: This might cause the compilation queue of the runtime context to be extended.
- void pushCombinedFunctionEntryLabel(Declaration const& _function);
+ /// If @a _runtimeOnly, the entry label will include the runtime assembly tag.
+ void pushCombinedFunctionEntryLabel(Declaration const& _function, bool _runtimeOnly = true);
/// Appends code for an implicit or explicit type conversion. This includes erasing higher
/// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory
@@ -200,6 +217,9 @@ public:
/// Creates a zero-value for the given type and puts it onto the stack. This might allocate
/// memory for memory references.
void pushZeroValue(Type const& _type);
+ /// Pushes a pointer to the stack that points to a (potentially shared) location in memory
+ /// that always contains a zero. It is not allowed to write there.
+ void pushZeroPointer();
/// Moves the value that is at the top of the stack to a stack variable.
void moveToStackVariable(VariableDeclaration const& _variable);
@@ -245,6 +265,10 @@ public:
/// Position of the free-memory-pointer in memory;
static const size_t freeMemoryPointer;
+ /// Position of the memory slot that is always zero.
+ static const size_t zeroPointer;
+ /// Starting offset for memory available to the user (aka the contract).
+ static const size_t generalPurposeMemoryStart;
private:
/// Address of the precompiled identity contract.
diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp
index 5a9498f0..0889ac7c 100644
--- a/libsolidity/codegen/ContractCompiler.cpp
+++ b/libsolidity/codegen/ContractCompiler.cpp
@@ -128,6 +128,7 @@ void ContractCompiler::appendCallValueCheck()
{
// Throw if function is not payable but call contained ether.
m_context << Instruction::CALLVALUE;
+ // TODO: error message?
m_context.appendConditionalRevert();
}
@@ -135,33 +136,13 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c
{
solAssert(!_contract.isLibrary(), "Tried to initialize library.");
CompilerContext::LocationSetter locationSetter(m_context, _contract);
- // Determine the arguments that are used for the base constructors.
- std::vector<ContractDefinition const*> const& bases = _contract.annotation().linearizedBaseContracts;
- for (ContractDefinition const* contract: bases)
- {
- if (FunctionDefinition const* constructor = contract->constructor())
- for (auto const& modifier: constructor->modifiers())
- {
- auto baseContract = dynamic_cast<ContractDefinition const*>(
- modifier->name()->annotation().referencedDeclaration);
- if (baseContract)
- if (m_baseArguments.count(baseContract->constructor()) == 0)
- m_baseArguments[baseContract->constructor()] = &modifier->arguments();
- }
- for (ASTPointer<InheritanceSpecifier> const& base: contract->baseContracts())
- {
- ContractDefinition const* baseContract = dynamic_cast<ContractDefinition const*>(
- base->name().annotation().referencedDeclaration
- );
- solAssert(baseContract, "");
+ m_baseArguments = &_contract.annotation().baseConstructorArguments;
- if (m_baseArguments.count(baseContract->constructor()) == 0)
- m_baseArguments[baseContract->constructor()] = &base->arguments();
- }
- }
// Initialization of state variables in base-to-derived order.
- for (ContractDefinition const* contract: boost::adaptors::reverse(bases))
+ for (ContractDefinition const* contract: boost::adaptors::reverse(
+ _contract.annotation().linearizedBaseContracts
+ ))
initializeStateVariables(*contract);
if (FunctionDefinition const* constructor = _contract.constructor())
@@ -235,9 +216,16 @@ void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _construc
FunctionType constructorType(_constructor);
if (!constructorType.parameterTypes().empty())
{
- solAssert(m_baseArguments.count(&_constructor), "");
- std::vector<ASTPointer<Expression>> const* arguments = m_baseArguments[&_constructor];
+ solAssert(m_baseArguments, "");
+ solAssert(m_baseArguments->count(&_constructor), "");
+ std::vector<ASTPointer<Expression>> const* arguments = nullptr;
+ ASTNode const* baseArgumentNode = m_baseArguments->at(&_constructor);
+ if (auto inheritanceSpecifier = dynamic_cast<InheritanceSpecifier const*>(baseArgumentNode))
+ arguments = inheritanceSpecifier->arguments();
+ else if (auto modifierInvocation = dynamic_cast<ModifierInvocation const*>(baseArgumentNode))
+ arguments = modifierInvocation->arguments();
solAssert(arguments, "");
+ solAssert(arguments->size() == constructorType.parameterTypes().size(), "");
for (unsigned i = 0; i < arguments->size(); ++i)
compileExpression(*(arguments->at(i)), constructorType.parameterTypes()[i]);
}
@@ -278,9 +266,10 @@ void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor)
m_context.appendProgramSize();
m_context << Instruction::DUP4 << Instruction::CODECOPY;
m_context << Instruction::DUP2 << Instruction::ADD;
+ m_context << Instruction::DUP1;
CompilerUtils(m_context).storeFreeMemoryPointer();
// stack: <memptr>
- appendCalldataUnpacker(FunctionType(_constructor).parameterTypes(), true);
+ CompilerUtils(m_context).abiDecode(FunctionType(_constructor).parameterTypes(), true);
}
_constructor.accept(*this);
}
@@ -339,6 +328,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
m_context << Instruction::STOP;
}
else
+ // TODO: error message here?
m_context.appendRevert();
for (auto const& it: interfaceFunctions)
@@ -367,7 +357,8 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
{
// Parameter for calldataUnpacker
m_context << CompilerUtils::dataStartOffset;
- appendCalldataUnpacker(functionType->parameterTypes());
+ m_context << Instruction::DUP1 << Instruction::CALLDATASIZE << Instruction::SUB;
+ CompilerUtils(m_context).abiDecode(functionType->parameterTypes());
}
m_context.appendJumpTo(m_context.functionEntryLabel(functionType->declaration()));
m_context << returnTag;
@@ -382,105 +373,6 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
}
}
-void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory)
-{
- // We do not check the calldata size, everything is zero-padded
-
- if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
- {
- // Use the new JULIA-based decoding function
- auto stackHeightBefore = m_context.stackHeight();
- CompilerUtils(m_context).abiDecodeV2(_typeParameters, _fromMemory);
- solAssert(m_context.stackHeight() - stackHeightBefore == CompilerUtils(m_context).sizeOnStack(_typeParameters) - 1, "");
- return;
- }
-
- //@todo this does not yet support nested dynamic arrays
-
- // Retain the offset pointer as base_offset, the point from which the data offsets are computed.
- m_context << Instruction::DUP1;
- for (TypePointer const& parameterType: _typeParameters)
- {
- // stack: v1 v2 ... v(k-1) base_offset current_offset
- TypePointer type = parameterType->decodingType();
- solUnimplementedAssert(type, "No decoding type found.");
- if (type->category() == Type::Category::Array)
- {
- auto const& arrayType = dynamic_cast<ArrayType const&>(*type);
- solUnimplementedAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented.");
- if (_fromMemory)
- {
- solUnimplementedAssert(
- arrayType.baseType()->isValueType(),
- "Nested memory arrays not yet implemented here."
- );
- // @todo If base type is an array or struct, it is still calldata-style encoded, so
- // we would have to convert it like below.
- solAssert(arrayType.location() == DataLocation::Memory, "");
- if (arrayType.isDynamicallySized())
- {
- // compute data pointer
- m_context << Instruction::DUP1 << Instruction::MLOAD;
- m_context << Instruction::DUP3 << Instruction::ADD;
- m_context << Instruction::SWAP2 << Instruction::SWAP1;
- m_context << u256(0x20) << Instruction::ADD;
- }
- else
- {
- m_context << Instruction::SWAP1 << Instruction::DUP2;
- m_context << u256(arrayType.calldataEncodedSize(true)) << Instruction::ADD;
- }
- }
- else
- {
- // first load from calldata and potentially convert to memory if arrayType is memory
- TypePointer calldataType = arrayType.copyForLocation(DataLocation::CallData, false);
- if (calldataType->isDynamicallySized())
- {
- // put on stack: data_pointer length
- CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory);
- // stack: base_offset data_offset next_pointer
- m_context << Instruction::SWAP1 << Instruction::DUP3 << Instruction::ADD;
- // stack: base_offset next_pointer data_pointer
- // retrieve length
- CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true);
- // stack: base_offset next_pointer length data_pointer
- m_context << Instruction::SWAP2;
- // stack: base_offset data_pointer length next_pointer
- }
- else
- {
- // leave the pointer on the stack
- m_context << Instruction::DUP1;
- m_context << u256(calldataType->calldataEncodedSize()) << Instruction::ADD;
- }
- if (arrayType.location() == DataLocation::Memory)
- {
- // stack: base_offset calldata_ref [length] next_calldata
- // copy to memory
- // move calldata type up again
- CompilerUtils(m_context).moveIntoStack(calldataType->sizeOnStack());
- CompilerUtils(m_context).convertType(*calldataType, arrayType, false, false, true);
- // fetch next pointer again
- CompilerUtils(m_context).moveToStackTop(arrayType.sizeOnStack());
- }
- // move base_offset up
- CompilerUtils(m_context).moveToStackTop(1 + arrayType.sizeOnStack());
- m_context << Instruction::SWAP1;
- }
- }
- else
- {
- solAssert(!type->isDynamicallySized(), "Unknown dynamically sized type: " + type->toString());
- CompilerUtils(m_context).loadFromMemoryDynamic(*type, !_fromMemory, true);
- CompilerUtils(m_context).moveToStackTop(1 + type->sizeOnStack());
- m_context << Instruction::SWAP1;
- }
- // stack: v1 v2 ... v(k-1) v(k) base_offset mem_offset
- }
- m_context << Instruction::POP << Instruction::POP;
-}
-
void ContractCompiler::appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary)
{
CompilerUtils utils(m_context);
@@ -1002,15 +894,21 @@ void ContractCompiler::appendModifierOrFunctionCode()
appendModifierOrFunctionCode();
else
{
- ModifierDefinition const& modifier = m_context.functionModifier(modifierInvocation->name()->name());
+ ModifierDefinition const& nonVirtualModifier = dynamic_cast<ModifierDefinition const&>(
+ *modifierInvocation->name()->annotation().referencedDeclaration
+ );
+ ModifierDefinition const& modifier = m_context.resolveVirtualFunctionModifier(nonVirtualModifier);
CompilerContext::LocationSetter locationSetter(m_context, modifier);
- solAssert(modifier.parameters().size() == modifierInvocation->arguments().size(), "");
+ std::vector<ASTPointer<Expression>> const& modifierArguments =
+ modifierInvocation->arguments() ? *modifierInvocation->arguments() : std::vector<ASTPointer<Expression>>();
+
+ solAssert(modifier.parameters().size() == modifierArguments.size(), "");
for (unsigned i = 0; i < modifier.parameters().size(); ++i)
{
m_context.addVariable(*modifier.parameters()[i]);
addedVariables.push_back(modifier.parameters()[i].get());
compileExpression(
- *modifierInvocation->arguments()[i],
+ *modifierArguments[i],
modifier.parameters()[i]->annotation().type
);
}
diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h
index 8559ea58..02a3452f 100644
--- a/libsolidity/codegen/ContractCompiler.h
+++ b/libsolidity/codegen/ContractCompiler.h
@@ -90,10 +90,6 @@ private:
void appendDelegatecallCheck();
void appendFunctionSelector(ContractDefinition const& _contract);
void appendCallValueCheck();
- /// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers.
- /// From memory if @a _fromMemory is true, otherwise from call data.
- /// Expects source offset on the stack, which is removed.
- void appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory = false);
void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary);
void registerStateVariables(ContractDefinition const& _contract);
@@ -139,7 +135,7 @@ private:
FunctionDefinition const* m_currentFunction = nullptr;
unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag
// arguments for base constructors, filled in derived-to-base order
- std::map<FunctionDefinition const*, std::vector<ASTPointer<Expression>> const*> m_baseArguments;
+ std::map<FunctionDefinition const*, ASTNode const*> const* m_baseArguments;
};
}
diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp
index 7162cb0d..3cf46a9d 100644
--- a/libsolidity/codegen/ExpressionCompiler.cpp
+++ b/libsolidity/codegen/ExpressionCompiler.cpp
@@ -33,6 +33,8 @@
#include <libsolidity/codegen/LValue.h>
#include <libevmasm/GasMeter.h>
+#include <libdevcore/Whiskers.h>
+
using namespace std;
namespace dev
@@ -139,8 +141,8 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
utils().popStackSlots(paramTypes.size() - 1);
}
unsigned retSizeOnStack = 0;
- solAssert(accessorType.returnParameterTypes().size() >= 1, "");
- auto const& returnTypes = accessorType.returnParameterTypes();
+ auto returnTypes = accessorType.returnParameterTypes();
+ solAssert(returnTypes.size() >= 1, "");
if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get()))
{
// remove offset
@@ -518,7 +520,23 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
arguments[i]->accept(*this);
utils().convertType(*arguments[i]->annotation().type, *function.parameterTypes()[i]);
}
- _functionCall.expression().accept(*this);
+
+ {
+ bool shortcutTaken = false;
+ if (auto identifier = dynamic_cast<Identifier const*>(&_functionCall.expression()))
+ if (auto functionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
+ {
+ // Do not directly visit the identifier, because this way, we can avoid
+ // the runtime entry label to be created at the creation time context.
+ CompilerContext::LocationSetter locationSetter2(m_context, *identifier);
+ utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef), false);
+ shortcutTaken = true;
+ }
+
+ if (!shortcutTaken)
+ _functionCall.expression().accept(*this);
+ }
+
unsigned parameterSize = CompilerUtils::sizeOnStack(function.parameterTypes());
if (function.bound())
{
@@ -592,7 +610,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::CREATE;
// Check if zero (out of stack or not enough balance).
m_context << Instruction::DUP1 << Instruction::ISZERO;
- m_context.appendConditionalRevert();
+ // TODO: Can we bubble up here? There might be different reasons for failure, I think.
+ m_context.appendConditionalRevert(true);
if (function.valueSet())
m_context << swapInstruction(1) << Instruction::POP;
break;
@@ -654,8 +673,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
if (function.kind() == FunctionType::Kind::Transfer)
{
// Check if zero (out of stack or not enough balance).
+ // TODO: bubble up here, but might also be different error.
m_context << Instruction::ISZERO;
- m_context.appendConditionalRevert();
+ m_context.appendConditionalRevert(true);
}
break;
case FunctionType::Kind::Selfdestruct:
@@ -664,8 +684,19 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::SELFDESTRUCT;
break;
case FunctionType::Kind::Revert:
- m_context.appendRevert();
+ {
+ if (!arguments.empty())
+ {
+ // function-sel(Error(string)) + encoding
+ solAssert(arguments.size() == 1, "");
+ solAssert(function.parameterTypes().size() == 1, "");
+ arguments.front()->accept(*this);
+ utils().revertWithStringData(*arguments.front()->annotation().type);
+ }
+ else
+ m_context.appendRevert();
break;
+ }
case FunctionType::Kind::SHA3:
{
TypePointers argumentTypes;
@@ -805,24 +836,27 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
function.kind() == FunctionType::Kind::ArrayPush ?
make_shared<ArrayType>(DataLocation::Storage, paramType) :
make_shared<ArrayType>(DataLocation::Storage);
- // get the current length
- ArrayUtils(m_context).retrieveLength(*arrayType);
- m_context << Instruction::DUP1;
- // stack: ArrayReference currentLength currentLength
- m_context << u256(1) << Instruction::ADD;
- // stack: ArrayReference currentLength newLength
- m_context << Instruction::DUP3 << Instruction::DUP2;
- ArrayUtils(m_context).resizeDynamicArray(*arrayType);
- m_context << Instruction::SWAP2 << Instruction::SWAP1;
- // stack: newLength ArrayReference oldLength
- ArrayUtils(m_context).accessIndex(*arrayType, false);
- // stack: newLength storageSlot slotOffset
+ // stack: ArrayReference
arguments[0]->accept(*this);
+ TypePointer const& argType = arguments[0]->annotation().type;
+ // stack: ArrayReference argValue
+ utils().moveToStackTop(argType->sizeOnStack(), 1);
+ // stack: argValue ArrayReference
+ m_context << Instruction::DUP1;
+ ArrayUtils(m_context).incrementDynamicArraySize(*arrayType);
+ // stack: argValue ArrayReference newLength
+ m_context << Instruction::SWAP1;
+ // stack: argValue newLength ArrayReference
+ m_context << u256(1) << Instruction::DUP3 << Instruction::SUB;
+ // stack: argValue newLength ArrayReference (newLength-1)
+ ArrayUtils(m_context).accessIndex(*arrayType, false);
+ // stack: argValue newLength storageSlot slotOffset
+ utils().moveToStackTop(3, argType->sizeOnStack());
// stack: newLength storageSlot slotOffset argValue
TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType());
solAssert(type, "");
- utils().convertType(*arguments[0]->annotation().type, *type);
+ utils().convertType(*argType, *type);
utils().moveToStackTop(1 + type->sizeOnStack());
utils().moveToStackTop(1 + type->sizeOnStack());
// stack: newLength argValue storageSlot slotOffset
@@ -834,8 +868,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
}
case FunctionType::Kind::ObjectCreation:
{
- // Will allocate at the end of memory (MSIZE) and not write at all unless the base
- // type is dynamically sized.
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_functionCall.annotation().type);
_functionCall.expression().accept(*this);
solAssert(arguments.size() == 1, "");
@@ -845,15 +877,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
utils().convertType(*arguments[0]->annotation().type, IntegerType(256));
// Stack: requested_length
- // Allocate at max(MSIZE, freeMemoryPointer)
utils().fetchFreeMemoryPointer();
- m_context << Instruction::DUP1 << Instruction::MSIZE;
- m_context << Instruction::LT;
- auto initialise = m_context.appendConditionalJump();
- // Free memory pointer does not point to empty memory, use MSIZE.
- m_context << Instruction::POP;
- m_context << Instruction::MSIZE;
- m_context << initialise;
// Stack: requested_length memptr
m_context << Instruction::SWAP1;
@@ -878,13 +902,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// Check if length is zero
m_context << Instruction::DUP1 << Instruction::ISZERO;
auto skipInit = m_context.appendConditionalJump();
-
- // We only have to initialise if the base type is a not a value type.
- if (dynamic_cast<ReferenceType const*>(arrayType.baseType().get()))
- {
- m_context << Instruction::DUP2 << u256(32) << Instruction::ADD;
- utils().zeroInitialiseMemoryArray(arrayType);
- }
+ // Always initialize because the free memory pointer might point at
+ // a dirty memory area.
+ m_context << Instruction::DUP2 << u256(32) << Instruction::ADD;
+ utils().zeroInitialiseMemoryArray(arrayType);
m_context << skipInit;
m_context << Instruction::POP;
break;
@@ -894,16 +915,130 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{
arguments.front()->accept(*this);
utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false);
+ if (arguments.size() > 1)
+ {
+ // Users probably expect the second argument to be evaluated
+ // even if the condition is false, as would be the case for an actual
+ // function call.
+ solAssert(arguments.size() == 2, "");
+ solAssert(function.kind() == FunctionType::Kind::Require, "");
+ arguments.at(1)->accept(*this);
+ utils().moveIntoStack(1, arguments.at(1)->annotation().type->sizeOnStack());
+ }
+ // Stack: <error string (unconverted)> <condition>
// jump if condition was met
m_context << Instruction::ISZERO << Instruction::ISZERO;
auto success = m_context.appendConditionalJump();
if (function.kind() == FunctionType::Kind::Assert)
// condition was not met, flag an error
m_context.appendInvalid();
+ else if (arguments.size() > 1)
+ utils().revertWithStringData(*arguments.at(1)->annotation().type);
else
m_context.appendRevert();
// the success branch
m_context << success;
+ if (arguments.size() > 1)
+ utils().popStackElement(*arguments.at(1)->annotation().type);
+ break;
+ }
+ case FunctionType::Kind::ABIEncode:
+ case FunctionType::Kind::ABIEncodePacked:
+ case FunctionType::Kind::ABIEncodeWithSelector:
+ case FunctionType::Kind::ABIEncodeWithSignature:
+ {
+ bool const isPacked = function.kind() == FunctionType::Kind::ABIEncodePacked;
+ bool const hasSelectorOrSignature =
+ function.kind() == FunctionType::Kind::ABIEncodeWithSelector ||
+ function.kind() == FunctionType::Kind::ABIEncodeWithSignature;
+
+ TypePointers argumentTypes;
+ TypePointers targetTypes;
+ for (unsigned i = 0; i < arguments.size(); ++i)
+ {
+ arguments[i]->accept(*this);
+ // Do not keep the selector as part of the ABI encoded args
+ if (!hasSelectorOrSignature || i > 0)
+ argumentTypes.push_back(arguments[i]->annotation().type);
+ }
+ utils().fetchFreeMemoryPointer();
+ // stack now: [<selector>] <arg1> .. <argN> <free_mem>
+
+ // adjust by 32(+4) bytes to accommodate the length(+selector)
+ m_context << u256(32 + (hasSelectorOrSignature ? 4 : 0)) << Instruction::ADD;
+ // stack now: [<selector>] <arg1> .. <argN> <data_encoding_area_start>
+
+ if (isPacked)
+ {
+ solAssert(!function.padArguments(), "");
+ utils().packedEncode(argumentTypes, TypePointers());
+ }
+ else
+ {
+ solAssert(function.padArguments(), "");
+ utils().abiEncode(argumentTypes, TypePointers());
+ }
+ utils().fetchFreeMemoryPointer();
+ // stack: [<selector>] <data_encoding_area_end> <bytes_memory_ptr>
+
+ // size is end minus start minus length slot
+ m_context.appendInlineAssembly(R"({
+ mstore(mem_ptr, sub(sub(mem_end, mem_ptr), 0x20))
+ })", {"mem_end", "mem_ptr"});
+ m_context << Instruction::SWAP1;
+ utils().storeFreeMemoryPointer();
+ // stack: [<selector>] <memory ptr>
+
+ if (hasSelectorOrSignature)
+ {
+ // stack: <selector> <memory pointer>
+ solAssert(arguments.size() >= 1, "");
+ TypePointer const& selectorType = arguments[0]->annotation().type;
+ utils().moveIntoStack(selectorType->sizeOnStack());
+ TypePointer dataOnStack = selectorType;
+ // stack: <memory pointer> <selector>
+ if (function.kind() == FunctionType::Kind::ABIEncodeWithSignature)
+ {
+ // hash the signature
+ if (auto const* stringType = dynamic_cast<StringLiteralType const*>(selectorType.get()))
+ {
+ FixedHash<4> hash(dev::keccak256(stringType->value()));
+ m_context << (u256(FixedHash<4>::Arith(hash)) << (256 - 32));
+ dataOnStack = make_shared<FixedBytesType>(4);
+ }
+ else
+ {
+ utils().fetchFreeMemoryPointer();
+ // stack: <memory pointer> <selector> <free mem ptr>
+ utils().packedEncode(TypePointers{selectorType}, TypePointers());
+ utils().toSizeAfterFreeMemoryPointer();
+ m_context << Instruction::KECCAK256;
+ // stack: <memory pointer> <hash>
+
+ dataOnStack = make_shared<FixedBytesType>(32);
+ }
+ }
+ else
+ {
+ solAssert(function.kind() == FunctionType::Kind::ABIEncodeWithSelector, "");
+ }
+
+ utils().convertType(*dataOnStack, FixedBytesType(4), true);
+
+ // stack: <memory pointer> <selector>
+
+ // load current memory, mask and combine the selector
+ string mask = formatNumber((u256(-1) >> 32));
+ m_context.appendInlineAssembly(R"({
+ let data_start := add(mem_ptr, 0x20)
+ let data := mload(data_start)
+ let mask := )" + mask + R"(
+ mstore(data_start, or(and(data, mask), selector))
+ })", {"mem_ptr", "selector"});
+ m_context << Instruction::POP;
+ }
+
+ // stack now: <memory pointer>
break;
}
case FunctionType::Kind::GasLeft:
@@ -1147,6 +1282,9 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
else if (member == "sig")
m_context << u256(0) << Instruction::CALLDATALOAD
<< (u256(0xffffffff) << (256 - 32)) << Instruction::AND;
+ else if (member == "blockhash")
+ {
+ }
else
solAssert(false, "Unknown magic member.");
break;
@@ -1356,6 +1494,10 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
}
}
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
+ // If the identifier is called right away, this code is executed in visit(FunctionCall...), because
+ // we want to avoid having a reference to the runtime function entry point in the
+ // constructor context, since this would force the compiler to include unreferenced
+ // internal functions in the runtime contex.
utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef));
else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration))
appendVariable(*variable, static_cast<Expression const&>(_identifier));
@@ -1615,15 +1757,27 @@ void ExpressionCompiler::appendExternalFunctionCall(
m_context.experimentalFeatureActive(ExperimentalFeature::V050) &&
m_context.evmVersion().hasStaticCall();
+ bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned retSize = 0;
+ TypePointers returnTypes;
if (returnSuccessCondition)
retSize = 0; // return value actually is success condition
+ else if (haveReturndatacopy)
+ returnTypes = _functionType.returnParameterTypes();
else
- for (auto const& retType: _functionType.returnParameterTypes())
+ returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes();
+
+ bool dynamicReturnSize = false;
+ for (auto const& retType: returnTypes)
+ if (retType->isDynamicallyEncoded())
{
- solAssert(!retType->isDynamicallySized(), "Unable to return dynamic type from external call.");
- retSize += retType->calldataEncodedSize();
+ solAssert(haveReturndatacopy, "");
+ dynamicReturnSize = true;
+ retSize = 0;
+ break;
}
+ else
+ retSize += retType->calldataEncodedSize();
// Evaluate arguments.
TypePointers argumentTypes;
@@ -1755,6 +1909,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall)
{
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
+ // TODO: error message?
m_context.appendConditionalRevert();
existenceChecked = true;
}
@@ -1797,7 +1952,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
{
//Propagate error condition (if CALL pushes 0 on stack).
m_context << Instruction::ISZERO;
- m_context.appendConditionalRevert();
+ m_context.appendConditionalRevert(true);
}
utils().popStackSlots(remainsSize);
@@ -1821,20 +1976,42 @@ void ExpressionCompiler::appendExternalFunctionCall(
utils().fetchFreeMemoryPointer();
m_context << Instruction::SUB << Instruction::MLOAD;
}
- else if (!_functionType.returnParameterTypes().empty())
+ else if (!returnTypes.empty())
{
utils().fetchFreeMemoryPointer();
- bool memoryNeeded = false;
- for (auto const& retType: _functionType.returnParameterTypes())
+ // Stack: return_data_start
+
+ // The old decoder did not allocate any memory (i.e. did not touch the free
+ // memory pointer), but kept references to the return data for
+ // (statically-sized) arrays
+ bool needToUpdateFreeMemoryPtr = false;
+ if (dynamicReturnSize || m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
+ needToUpdateFreeMemoryPtr = true;
+ else
+ for (auto const& retType: returnTypes)
+ if (dynamic_cast<ReferenceType const*>(retType.get()))
+ needToUpdateFreeMemoryPtr = true;
+
+ // Stack: return_data_start
+ if (dynamicReturnSize)
{
- utils().loadFromMemoryDynamic(*retType, false, true, true);
- if (dynamic_cast<ReferenceType const*>(retType.get()))
- memoryNeeded = true;
+ solAssert(haveReturndatacopy, "");
+ m_context.appendInlineAssembly("{ returndatacopy(return_data_start, 0, returndatasize()) }", {"return_data_start"});
}
- if (memoryNeeded)
- utils().storeFreeMemoryPointer();
else
- m_context << Instruction::POP;
+ solAssert(retSize > 0, "");
+ // Always use the actual return length, and not our calculated expected length, if returndatacopy is supported.
+ // This ensures it can catch badly formatted input from external calls.
+ m_context << (haveReturndatacopy ? eth::AssemblyItem(Instruction::RETURNDATASIZE) : u256(retSize));
+ // Stack: return_data_start return_data_size
+ if (needToUpdateFreeMemoryPtr)
+ m_context.appendInlineAssembly(R"({
+ // round size to the next multiple of 32
+ let newMem := add(start, and(add(size, 0x1f), not(0x1f)))
+ mstore(0x40, newMem)
+ })", {"start", "size"});
+
+ utils().abiDecode(returnTypes, true, true);
}
}
diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp
index 1da5b291..8f4abdc2 100644
--- a/libsolidity/formal/SMTChecker.cpp
+++ b/libsolidity/formal/SMTChecker.cpp
@@ -205,7 +205,7 @@ void SMTChecker::endVisit(Assignment const& _assignment)
_assignment.location(),
"Assertion checker does not yet implement compound assignment."
);
- else if (_assignment.annotation().type->category() != Type::Category::Integer)
+ else if (!SSAVariable::isSupportedType(_assignment.annotation().type->category()))
m_errorReporter.warning(
_assignment.location(),
"Assertion checker does not yet implement type " + _assignment.annotation().type->toString()
@@ -266,14 +266,15 @@ void SMTChecker::endVisit(UnaryOperation const& _op)
{
case Token::Not: // !
{
- solAssert(_op.annotation().type->category() == Type::Category::Bool, "");
+ solAssert(SSAVariable::isBool(_op.annotation().type->category()), "");
defineExpr(_op, !expr(_op.subExpression()));
break;
}
case Token::Inc: // ++ (pre- or postfix)
case Token::Dec: // -- (pre- or postfix)
{
- solAssert(_op.annotation().type->category() == Type::Category::Integer, "");
+
+ solAssert(SSAVariable::isInteger(_op.annotation().type->category()), "");
solAssert(_op.subExpression().annotation().lValueRequested, "");
if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_op.subExpression()))
{
@@ -370,7 +371,7 @@ void SMTChecker::endVisit(Identifier const& _identifier)
{
// Will be translated as part of the node that requested the lvalue.
}
- else if (SSAVariable::supportedType(_identifier.annotation().type.get()))
+ else if (SSAVariable::isSupportedType(_identifier.annotation().type->category()))
defineExpr(_identifier, currentValue(*decl));
else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get()))
{
@@ -444,21 +445,37 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op)
void SMTChecker::compareOperation(BinaryOperation const& _op)
{
solAssert(_op.annotation().commonType, "");
- if (_op.annotation().commonType->category() == Type::Category::Integer)
+ if (SSAVariable::isSupportedType(_op.annotation().commonType->category()))
{
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)
- );
+ shared_ptr<smt::Expression> value;
+ if (SSAVariable::isInteger(_op.annotation().commonType->category()))
+ {
+ value = make_shared<smt::Expression>(
+ 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)
+ );
+ }
+ else // Bool
+ {
+ solAssert(SSAVariable::isBool(_op.annotation().commonType->category()), "");
+ value = make_shared<smt::Expression>(
+ 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.
- defineExpr(_op, value);
+ defineExpr(_op, *value);
}
else
m_errorReporter.warning(
@@ -728,10 +745,10 @@ void SMTChecker::mergeVariables(vector<Declaration const*> const& _variables, sm
bool SMTChecker::createVariable(VariableDeclaration const& _varDecl)
{
- if (SSAVariable::supportedType(_varDecl.type().get()))
+ if (SSAVariable::isSupportedType(_varDecl.type()->category()))
{
solAssert(m_variables.count(&_varDecl) == 0, "");
- m_variables.emplace(&_varDecl, SSAVariable(&_varDecl, *m_interface));
+ m_variables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface));
return true;
}
else
diff --git a/libsolidity/formal/SSAVariable.cpp b/libsolidity/formal/SSAVariable.cpp
index 4e6bcbcb..f3213e03 100644
--- a/libsolidity/formal/SSAVariable.cpp
+++ b/libsolidity/formal/SSAVariable.cpp
@@ -17,6 +17,7 @@
#include <libsolidity/formal/SSAVariable.h>
+#include <libsolidity/formal/SymbolicBoolVariable.h>
#include <libsolidity/formal/SymbolicIntVariable.h>
#include <libsolidity/ast/AST.h>
@@ -26,23 +27,35 @@ using namespace dev;
using namespace dev::solidity;
SSAVariable::SSAVariable(
- Declaration const* _decl,
+ Declaration const& _decl,
smt::SolverInterface& _interface
)
{
resetIndex();
- if (dynamic_cast<IntegerType const*>(_decl->type().get()))
+ if (isInteger(_decl.type()->category()))
m_symbolicVar = make_shared<SymbolicIntVariable>(_decl, _interface);
+ else if (isBool(_decl.type()->category()))
+ m_symbolicVar = make_shared<SymbolicBoolVariable>(_decl, _interface);
else
{
solAssert(false, "");
}
}
-bool SSAVariable::supportedType(Type const* _decl)
+bool SSAVariable::isSupportedType(Type::Category _category)
{
- return dynamic_cast<IntegerType const*>(_decl);
+ return isInteger(_category) || isBool(_category);
+}
+
+bool SSAVariable::isInteger(Type::Category _category)
+{
+ return _category == Type::Category::Integer;
+}
+
+bool SSAVariable::isBool(Type::Category _category)
+{
+ return _category == Type::Category::Bool;
}
void SSAVariable::resetIndex()
diff --git a/libsolidity/formal/SSAVariable.h b/libsolidity/formal/SSAVariable.h
index 275e8590..bf5dae3b 100644
--- a/libsolidity/formal/SSAVariable.h
+++ b/libsolidity/formal/SSAVariable.h
@@ -37,7 +37,7 @@ public:
/// @param _decl Used to determine the type and forwarded to the symbolic var.
/// @param _interface Forwarded to the symbolic var such that it can give constraints to the solver.
SSAVariable(
- Declaration const* _decl,
+ Declaration const& _decl,
smt::SolverInterface& _interface
);
@@ -68,8 +68,10 @@ public:
void setZeroValue();
void setUnknownValue();
- /// So far Int is supported.
- static bool supportedType(Type const* _decl);
+ /// So far Int and Bool are supported.
+ static bool isSupportedType(Type::Category _category);
+ static bool isInteger(Type::Category _category);
+ static bool isBool(Type::Category _category);
private:
smt::Expression valueAtSequence(int _seq) const
diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h
index 88487310..0bdebb6c 100644
--- a/libsolidity/formal/SolverInterface.h
+++ b/libsolidity/formal/SolverInterface.h
@@ -46,7 +46,8 @@ enum class Sort
{
Int,
Bool,
- IntIntFun // Function of one Int returning a single Int
+ IntIntFun, // Function of one Int returning a single Int
+ IntBoolFun // Function of one Int returning a single Bool
};
/// C++ representation of an SMTLIB2 expression.
@@ -132,10 +133,22 @@ public:
Expression operator()(Expression _a) const
{
solAssert(
- sort == Sort::IntIntFun && arguments.empty(),
+ arguments.empty(),
"Attempted function application to non-function."
);
- return Expression(name, _a, Sort::Int);
+ switch (sort)
+ {
+ case Sort::IntIntFun:
+ return Expression(name, _a, Sort::Int);
+ case Sort::IntBoolFun:
+ return Expression(name, _a, Sort::Bool);
+ default:
+ solAssert(
+ false,
+ "Attempted function application to invalid type."
+ );
+ break;
+ }
}
std::string const name;
@@ -167,9 +180,18 @@ public:
virtual Expression newFunction(std::string _name, Sort _domain, Sort _codomain)
{
- solAssert(_domain == Sort::Int && _codomain == Sort::Int, "Function sort not supported.");
+ solAssert(_domain == Sort::Int, "Function sort not supported.");
// Subclasses should do something here
- return Expression(std::move(_name), {}, Sort::IntIntFun);
+ switch (_codomain)
+ {
+ case Sort::Int:
+ return Expression(std::move(_name), {}, Sort::IntIntFun);
+ case Sort::Bool:
+ return Expression(std::move(_name), {}, Sort::IntBoolFun);
+ default:
+ solAssert(false, "Function sort not supported.");
+ break;
+ }
}
virtual Expression newInteger(std::string _name)
{
diff --git a/libsolidity/formal/SymbolicBoolVariable.cpp b/libsolidity/formal/SymbolicBoolVariable.cpp
new file mode 100644
index 00000000..5cf22d7d
--- /dev/null
+++ b/libsolidity/formal/SymbolicBoolVariable.cpp
@@ -0,0 +1,47 @@
+/*
+ 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/SymbolicBoolVariable.h>
+
+#include <libsolidity/ast/AST.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+SymbolicBoolVariable::SymbolicBoolVariable(
+ Declaration const& _decl,
+ smt::SolverInterface&_interface
+):
+ SymbolicVariable(_decl, _interface)
+{
+ solAssert(m_declaration.type()->category() == Type::Category::Bool, "");
+}
+
+smt::Expression SymbolicBoolVariable::valueAtSequence(int _seq) const
+{
+ return m_interface.newBool(uniqueSymbol(_seq));
+}
+
+void SymbolicBoolVariable::setZeroValue(int _seq)
+{
+ m_interface.addAssertion(valueAtSequence(_seq) == smt::Expression(false));
+}
+
+void SymbolicBoolVariable::setUnknownValue(int)
+{
+}
diff --git a/libsolidity/formal/SymbolicBoolVariable.h b/libsolidity/formal/SymbolicBoolVariable.h
new file mode 100644
index 00000000..678f97d9
--- /dev/null
+++ b/libsolidity/formal/SymbolicBoolVariable.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of solidity.
+
+ solidity is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ solidity is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <libsolidity/formal/SymbolicVariable.h>
+
+#include <libsolidity/ast/Types.h>
+
+namespace dev
+{
+namespace solidity
+{
+
+/**
+ * Specialization of SymbolicVariable for Bool
+ */
+class SymbolicBoolVariable: public SymbolicVariable
+{
+public:
+ SymbolicBoolVariable(
+ Declaration const& _decl,
+ smt::SolverInterface& _interface
+ );
+
+ /// Sets the var to false.
+ void setZeroValue(int _seq);
+ /// Does nothing since the SMT solver already knows the valid values.
+ void setUnknownValue(int _seq);
+
+protected:
+ smt::Expression valueAtSequence(int _seq) const;
+};
+
+}
+}
diff --git a/libsolidity/formal/SymbolicIntVariable.cpp b/libsolidity/formal/SymbolicIntVariable.cpp
index d08dc155..5e71fdcc 100644
--- a/libsolidity/formal/SymbolicIntVariable.cpp
+++ b/libsolidity/formal/SymbolicIntVariable.cpp
@@ -24,13 +24,17 @@ using namespace dev;
using namespace dev::solidity;
SymbolicIntVariable::SymbolicIntVariable(
- Declaration const* _decl,
+ Declaration const& _decl,
smt::SolverInterface& _interface
):
SymbolicVariable(_decl, _interface)
{
- solAssert(m_declaration->type()->category() == Type::Category::Integer, "");
- m_expression = make_shared<smt::Expression>(m_interface.newFunction(uniqueSymbol(), smt::Sort::Int, smt::Sort::Int));
+ solAssert(m_declaration.type()->category() == Type::Category::Integer, "");
+}
+
+smt::Expression SymbolicIntVariable::valueAtSequence(int _seq) const
+{
+ return m_interface.newInteger(uniqueSymbol(_seq));
}
void SymbolicIntVariable::setZeroValue(int _seq)
@@ -40,7 +44,7 @@ void SymbolicIntVariable::setZeroValue(int _seq)
void SymbolicIntVariable::setUnknownValue(int _seq)
{
- auto const& intType = dynamic_cast<IntegerType const&>(*m_declaration->type());
+ auto const& intType = dynamic_cast<IntegerType const&>(*m_declaration.type());
m_interface.addAssertion(valueAtSequence(_seq) >= minValue(intType));
m_interface.addAssertion(valueAtSequence(_seq) <= maxValue(intType));
}
diff --git a/libsolidity/formal/SymbolicIntVariable.h b/libsolidity/formal/SymbolicIntVariable.h
index afa25f1b..d591e8db 100644
--- a/libsolidity/formal/SymbolicIntVariable.h
+++ b/libsolidity/formal/SymbolicIntVariable.h
@@ -33,7 +33,7 @@ class SymbolicIntVariable: public SymbolicVariable
{
public:
SymbolicIntVariable(
- Declaration const* _decl,
+ Declaration const& _decl,
smt::SolverInterface& _interface
);
@@ -44,6 +44,9 @@ public:
static smt::Expression minValue(IntegerType const& _t);
static smt::Expression maxValue(IntegerType const& _t);
+
+protected:
+ smt::Expression valueAtSequence(int _seq) const;
};
}
diff --git a/libsolidity/formal/SymbolicVariable.cpp b/libsolidity/formal/SymbolicVariable.cpp
index 629049ea..caefa3a3 100644
--- a/libsolidity/formal/SymbolicVariable.cpp
+++ b/libsolidity/formal/SymbolicVariable.cpp
@@ -24,7 +24,7 @@ using namespace dev;
using namespace dev::solidity;
SymbolicVariable::SymbolicVariable(
- Declaration const* _decl,
+ Declaration const& _decl,
smt::SolverInterface& _interface
):
m_declaration(_decl),
@@ -32,9 +32,9 @@ SymbolicVariable::SymbolicVariable(
{
}
-string SymbolicVariable::uniqueSymbol() const
+string SymbolicVariable::uniqueSymbol(int _seq) const
{
- return m_declaration->name() + "_" + to_string(m_declaration->id());
+ return m_declaration.name() + "_" + to_string(m_declaration.id()) + "_" + to_string(_seq);
}
diff --git a/libsolidity/formal/SymbolicVariable.h b/libsolidity/formal/SymbolicVariable.h
index 93258250..e4e4ea8d 100644
--- a/libsolidity/formal/SymbolicVariable.h
+++ b/libsolidity/formal/SymbolicVariable.h
@@ -37,7 +37,7 @@ class SymbolicVariable
{
public:
SymbolicVariable(
- Declaration const* _decl,
+ Declaration const& _decl,
smt::SolverInterface& _interface
);
@@ -46,7 +46,7 @@ public:
return valueAtSequence(_seq);
}
- std::string uniqueSymbol() const;
+ std::string uniqueSymbol(int _seq) const;
/// Sets the var to the default value of its type.
virtual void setZeroValue(int _seq) = 0;
@@ -55,13 +55,9 @@ public:
virtual void setUnknownValue(int _seq) = 0;
protected:
- smt::Expression valueAtSequence(int _seq) const
- {
- return (*m_expression)(_seq);
- }
+ virtual smt::Expression valueAtSequence(int _seq) const = 0;
- Declaration const* m_declaration;
- std::shared_ptr<smt::Expression> m_expression = nullptr;
+ Declaration const& m_declaration;
smt::SolverInterface& m_interface;
};
diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp
index eacfca9c..4ff14aa2 100644
--- a/libsolidity/interface/CompilerStack.cpp
+++ b/libsolidity/interface/CompilerStack.cpp
@@ -164,85 +164,94 @@ bool CompilerStack::analyze()
resolveImports();
bool noErrors = true;
- SyntaxChecker syntaxChecker(m_errorReporter);
- for (Source const* source: m_sourceOrder)
- if (!syntaxChecker.checkSyntax(*source->ast))
- noErrors = false;
-
- DocStringAnalyser docStringAnalyser(m_errorReporter);
- for (Source const* source: m_sourceOrder)
- if (!docStringAnalyser.analyseDocStrings(*source->ast))
- noErrors = false;
- m_globalContext = make_shared<GlobalContext>();
- NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errorReporter);
- for (Source const* source: m_sourceOrder)
- if (!resolver.registerDeclarations(*source->ast))
- return false;
-
- map<string, SourceUnit const*> sourceUnitsByName;
- for (auto& source: m_sources)
- sourceUnitsByName[source.first] = source.second.ast.get();
- for (Source const* source: m_sourceOrder)
- if (!resolver.performImports(*source->ast, sourceUnitsByName))
- return false;
+ try {
+ SyntaxChecker syntaxChecker(m_errorReporter);
+ for (Source const* source: m_sourceOrder)
+ if (!syntaxChecker.checkSyntax(*source->ast))
+ noErrors = false;
- for (Source const* source: m_sourceOrder)
- for (ASTPointer<ASTNode> const& node: source->ast->nodes())
- if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
- {
- m_globalContext->setCurrentContract(*contract);
- if (!resolver.updateDeclaration(*m_globalContext->currentThis())) return false;
- if (!resolver.updateDeclaration(*m_globalContext->currentSuper())) return false;
- if (!resolver.resolveNamesAndTypes(*contract)) return false;
-
- // Note that we now reference contracts by their fully qualified names, and
- // thus contracts can only conflict if declared in the same source file. This
- // already causes a double-declaration error elsewhere, so we do not report
- // an error here and instead silently drop any additional contracts we find.
-
- if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end())
- m_contracts[contract->fullyQualifiedName()].contract = contract;
- }
+ DocStringAnalyser docStringAnalyser(m_errorReporter);
+ for (Source const* source: m_sourceOrder)
+ if (!docStringAnalyser.analyseDocStrings(*source->ast))
+ noErrors = false;
- TypeChecker typeChecker(m_evmVersion, m_errorReporter);
- for (Source const* source: m_sourceOrder)
- for (ASTPointer<ASTNode> const& node: source->ast->nodes())
- if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
- if (!typeChecker.checkTypeRequirements(*contract))
- noErrors = false;
+ m_globalContext = make_shared<GlobalContext>();
+ NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errorReporter);
+ for (Source const* source: m_sourceOrder)
+ if (!resolver.registerDeclarations(*source->ast))
+ return false;
- if (noErrors)
- {
- PostTypeChecker postTypeChecker(m_errorReporter);
+ map<string, SourceUnit const*> sourceUnitsByName;
+ for (auto& source: m_sources)
+ sourceUnitsByName[source.first] = source.second.ast.get();
for (Source const* source: m_sourceOrder)
- if (!postTypeChecker.check(*source->ast))
- noErrors = false;
- }
+ if (!resolver.performImports(*source->ast, sourceUnitsByName))
+ return false;
- if (noErrors)
- {
- StaticAnalyzer staticAnalyzer(m_errorReporter);
for (Source const* source: m_sourceOrder)
- if (!staticAnalyzer.analyze(*source->ast))
- noErrors = false;
- }
+ for (ASTPointer<ASTNode> const& node: source->ast->nodes())
+ if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
+ {
+ m_globalContext->setCurrentContract(*contract);
+ if (!resolver.updateDeclaration(*m_globalContext->currentThis())) return false;
+ if (!resolver.updateDeclaration(*m_globalContext->currentSuper())) return false;
+ if (!resolver.resolveNamesAndTypes(*contract)) return false;
+
+ // Note that we now reference contracts by their fully qualified names, and
+ // thus contracts can only conflict if declared in the same source file. This
+ // already causes a double-declaration error elsewhere, so we do not report
+ // an error here and instead silently drop any additional contracts we find.
+
+ if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end())
+ m_contracts[contract->fullyQualifiedName()].contract = contract;
+ }
- if (noErrors)
- {
- vector<ASTPointer<ASTNode>> ast;
+ TypeChecker typeChecker(m_evmVersion, m_errorReporter);
for (Source const* source: m_sourceOrder)
- ast.push_back(source->ast);
+ for (ASTPointer<ASTNode> const& node: source->ast->nodes())
+ if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
+ if (!typeChecker.checkTypeRequirements(*contract))
+ noErrors = false;
- if (!ViewPureChecker(ast, m_errorReporter).check())
- noErrors = false;
- }
+ if (noErrors)
+ {
+ PostTypeChecker postTypeChecker(m_errorReporter);
+ for (Source const* source: m_sourceOrder)
+ if (!postTypeChecker.check(*source->ast))
+ noErrors = false;
+ }
- if (noErrors)
+ if (noErrors)
+ {
+ StaticAnalyzer staticAnalyzer(m_errorReporter);
+ for (Source const* source: m_sourceOrder)
+ if (!staticAnalyzer.analyze(*source->ast))
+ noErrors = false;
+ }
+
+ if (noErrors)
+ {
+ vector<ASTPointer<ASTNode>> ast;
+ for (Source const* source: m_sourceOrder)
+ ast.push_back(source->ast);
+
+ if (!ViewPureChecker(ast, m_errorReporter).check())
+ noErrors = false;
+ }
+
+ if (noErrors)
+ {
+ SMTChecker smtChecker(m_errorReporter, m_smtQuery);
+ for (Source const* source: m_sourceOrder)
+ smtChecker.analyze(*source->ast);
+ }
+ }
+ catch(FatalError const&)
{
- SMTChecker smtChecker(m_errorReporter, m_smtQuery);
- for (Source const* source: m_sourceOrder)
- smtChecker.analyze(*source->ast);
+ if (m_errorReporter.errors().empty())
+ throw; // Something is weird here, rather throw again.
+ noErrors = false;
}
if (noErrors)
diff --git a/libsolidity/interface/EVMVersion.h b/libsolidity/interface/EVMVersion.h
index 13c4ec94..b68e1f4e 100644
--- a/libsolidity/interface/EVMVersion.h
+++ b/libsolidity/interface/EVMVersion.h
@@ -49,7 +49,7 @@ public:
static boost::optional<EVMVersion> fromString(std::string const& _version)
{
- for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium()})
+ for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium(), constantinople()})
if (_version == v.name())
return v;
return {};
diff --git a/libsolidity/interface/ErrorReporter.cpp b/libsolidity/interface/ErrorReporter.cpp
index e6171756..368e25e0 100644
--- a/libsolidity/interface/ErrorReporter.cpp
+++ b/libsolidity/interface/ErrorReporter.cpp
@@ -61,6 +61,9 @@ void ErrorReporter::warning(
void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, string const& _description)
{
+ if (checkForExcessiveErrors(_type))
+ return;
+
auto err = make_shared<Error>(_type);
*err <<
errinfo_sourceLocation(_location) <<
@@ -71,6 +74,9 @@ void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, st
void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description)
{
+ if (checkForExcessiveErrors(_type))
+ return;
+
auto err = make_shared<Error>(_type);
*err <<
errinfo_sourceLocation(_location) <<
@@ -80,6 +86,37 @@ void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, Se
m_errorList.push_back(err);
}
+bool ErrorReporter::checkForExcessiveErrors(Error::Type _type)
+{
+ if (_type == Error::Type::Warning)
+ {
+ m_warningCount++;
+
+ if (m_warningCount == c_maxWarningsAllowed)
+ {
+ auto err = make_shared<Error>(Error::Type::Warning);
+ *err << errinfo_comment("There are more than 256 warnings. Ignoring the rest.");
+ m_errorList.push_back(err);
+ }
+
+ if (m_warningCount >= c_maxWarningsAllowed)
+ return true;
+ }
+ else
+ {
+ m_errorCount++;
+
+ if (m_errorCount > c_maxErrorsAllowed)
+ {
+ auto err = make_shared<Error>(Error::Type::Warning);
+ *err << errinfo_comment("There are more than 256 errors. Aborting.");
+ m_errorList.push_back(err);
+ BOOST_THROW_EXCEPTION(FatalError());
+ }
+ }
+
+ return false;
+}
void ErrorReporter::fatalError(Error::Type _type, SourceLocation const& _location, string const& _description)
{
diff --git a/libsolidity/interface/ErrorReporter.h b/libsolidity/interface/ErrorReporter.h
index a87db21d..d1a0030f 100644
--- a/libsolidity/interface/ErrorReporter.h
+++ b/libsolidity/interface/ErrorReporter.h
@@ -102,7 +102,16 @@ private:
SourceLocation const& _location = SourceLocation(),
std::string const& _description = std::string());
+ // @returns true if error shouldn't be stored
+ bool checkForExcessiveErrors(Error::Type _type);
+
ErrorList& m_errorList;
+
+ unsigned m_errorCount = 0;
+ unsigned m_warningCount = 0;
+
+ const unsigned c_maxWarningsAllowed = 256;
+ const unsigned c_maxErrorsAllowed = 256;
};
diff --git a/libsolidity/interface/GasEstimator.cpp b/libsolidity/interface/GasEstimator.cpp
index 2139395f..a496cc21 100644
--- a/libsolidity/interface/GasEstimator.cpp
+++ b/libsolidity/interface/GasEstimator.cpp
@@ -136,12 +136,19 @@ GasEstimator::GasConsumption GasEstimator::functionalEstimation(
ExpressionClasses& classes = state->expressionClasses();
using Id = ExpressionClasses::Id;
using Ids = vector<Id>;
+ // div(calldataload(0), 1 << 224) equals to hashValue
Id hashValue = classes.find(u256(FixedHash<4>::Arith(FixedHash<4>(dev::keccak256(_signature)))));
Id calldata = classes.find(Instruction::CALLDATALOAD, Ids{classes.find(u256(0))});
classes.forceEqual(hashValue, Instruction::DIV, Ids{
calldata,
- classes.find(u256(1) << (8 * 28))
+ classes.find(u256(1) << 224)
});
+ // lt(calldatasize(), 4) equals to 0 (ignore the shortcut for fallback functions)
+ classes.forceEqual(
+ classes.find(u256(0)),
+ Instruction::LT,
+ Ids{classes.find(Instruction::CALLDATASIZE), classes.find(u256(4))}
+ );
}
PathGasMeter meter(_items, m_evmVersion);
diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp
index 0409de72..d058d556 100644
--- a/libsolidity/parsing/DocStringParser.cpp
+++ b/libsolidity/parsing/DocStringParser.cpp
@@ -119,21 +119,17 @@ DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end)
return _end;
}
auto nameEndPos = firstSpaceOrTab(nameStartPos, _end);
- if (nameEndPos == _end)
- {
- appendError("End of param name not found: " + string(nameStartPos, _end));
- return _end;
- }
auto paramName = string(nameStartPos, nameEndPos);
auto descStartPos = skipWhitespace(nameEndPos, _end);
- if (descStartPos == _end)
+ auto nlPos = find(descStartPos, _end, '\n');
+
+ if (descStartPos == nlPos)
{
appendError("No description given for param " + paramName);
return _end;
}
- auto nlPos = find(descStartPos, _end, '\n');
auto paramDesc = string(descStartPos, nlPos);
newTag("param");
m_lastTag->paramName = paramName;
diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp
index 8c97f55f..618a0896 100644
--- a/libsolidity/parsing/Parser.cpp
+++ b/libsolidity/parsing/Parser.cpp
@@ -238,7 +238,10 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _exp
Token::Value currentTokenValue = m_scanner->currentToken();
if (currentTokenValue == Token::RBrace)
break;
- else if (currentTokenValue == Token::Function)
+ else if (
+ currentTokenValue == Token::Function ||
+ (currentTokenValue == Token::Identifier && m_scanner->currentLiteral() == "constructor")
+ )
// This can be a function or a state variable of function type (especially
// complicated to distinguish fallback function from function type state variable)
subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable(name.get()));
@@ -283,17 +286,17 @@ ASTPointer<InheritanceSpecifier> Parser::parseInheritanceSpecifier()
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
ASTPointer<UserDefinedTypeName> name(parseUserDefinedTypeName());
- vector<ASTPointer<Expression>> arguments;
+ unique_ptr<vector<ASTPointer<Expression>>> arguments;
if (m_scanner->currentToken() == Token::LParen)
{
m_scanner->next();
- arguments = parseFunctionCallListArguments();
+ arguments.reset(new vector<ASTPointer<Expression>>(parseFunctionCallListArguments()));
nodeFactory.markEndPosition();
expectToken(Token::RParen);
}
else
nodeFactory.setEndPositionFromNode(name);
- return nodeFactory.createNode<InheritanceSpecifier>(name, arguments);
+ return nodeFactory.createNode<InheritanceSpecifier>(name, std::move(arguments));
}
Declaration::Visibility Parser::parseVisibilitySpecifier(Token::Value _token)
@@ -329,15 +332,31 @@ StateMutability Parser::parseStateMutability(Token::Value _token)
return stateMutability;
}
-Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers)
+Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(
+ bool _forceEmptyName,
+ bool _allowModifiers,
+ ASTString const* _contractName
+)
{
RecursionGuard recursionGuard(*this);
FunctionHeaderParserResult result;
- expectToken(Token::Function);
- if (_forceEmptyName || m_scanner->currentToken() == Token::LParen)
- result.name = make_shared<ASTString>(); // anonymous function
+
+ result.isConstructor = false;
+
+ if (m_scanner->currentToken() == Token::Identifier && m_scanner->currentLiteral() == "constructor")
+ result.isConstructor = true;
+ else if (m_scanner->currentToken() != Token::Function)
+ solAssert(false, "Function or constructor expected.");
+ m_scanner->next();
+
+ if (result.isConstructor || _forceEmptyName || m_scanner->currentToken() == Token::LParen)
+ result.name = make_shared<ASTString>();
else
result.name = expectIdentifierToken();
+
+ if (!result.name->empty() && _contractName && *result.name == *_contractName)
+ result.isConstructor = true;
+
VarDeclParserOptions options;
options.allowLocationSpecifier = true;
result.parameters = parseParameterList(options);
@@ -346,12 +365,13 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN
Token::Value token = m_scanner->currentToken();
if (_allowModifiers && token == Token::Identifier)
{
- // This can either be a modifier (function declaration) or the name of the
- // variable (function type name plus variable).
- if (
+ // If the name is empty (and this is not a constructor),
+ // then this can either be a modifier (fallback function declaration)
+ // or the name of the state variable (function type name plus variable).
+ if ((result.name->empty() && !result.isConstructor) && (
m_scanner->peekNextToken() == Token::Semicolon ||
m_scanner->peekNextToken() == Token::Assign
- )
+ ))
// Variable declaration, break here.
break;
else
@@ -361,6 +381,14 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN
{
if (result.visibility != Declaration::Visibility::Default)
{
+ // There is the special case of a public state variable of function type.
+ // Detect this and return early.
+ if (
+ (result.visibility == Declaration::Visibility::External || result.visibility == Declaration::Visibility::Internal) &&
+ result.modifiers.empty() &&
+ (result.name->empty() && !result.isConstructor)
+ )
+ break;
parserError(string(
"Visibility already specified as \"" +
Declaration::visibilityToString(result.visibility) +
@@ -407,9 +435,10 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A
if (m_scanner->currentCommentLiteral() != "")
docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral());
- FunctionHeaderParserResult header = parseFunctionHeader(false, true);
+ FunctionHeaderParserResult header = parseFunctionHeader(false, true, _contractName);
if (
+ header.isConstructor ||
!header.modifiers.empty() ||
!header.name->empty() ||
m_scanner->currentToken() == Token::Semicolon ||
@@ -426,12 +455,11 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A
}
else
m_scanner->next(); // just consume the ';'
- bool const c_isConstructor = (_contractName && *header.name == *_contractName);
return nodeFactory.createNode<FunctionDefinition>(
header.name,
header.visibility,
header.stateMutability,
- c_isConstructor,
+ header.isConstructor,
docstring,
header.parameters,
header.modifiers,
@@ -579,8 +607,10 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
if (_options.allowEmptyName && m_scanner->currentToken() != Token::Identifier)
{
identifier = make_shared<ASTString>("");
- solAssert(type != nullptr, "");
- nodeFactory.setEndPositionFromNode(type);
+ solAssert(!_options.allowVar, ""); // allowEmptyName && allowVar makes no sense
+ if (type)
+ nodeFactory.setEndPositionFromNode(type);
+ // if type is null this has already caused an error
}
else
identifier = expectIdentifierToken();
@@ -683,17 +713,17 @@ ASTPointer<ModifierInvocation> Parser::parseModifierInvocation()
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
ASTPointer<Identifier> name(parseIdentifier());
- vector<ASTPointer<Expression>> arguments;
+ unique_ptr<vector<ASTPointer<Expression>>> arguments;
if (m_scanner->currentToken() == Token::LParen)
{
m_scanner->next();
- arguments = parseFunctionCallListArguments();
+ arguments.reset(new vector<ASTPointer<Expression>>(parseFunctionCallListArguments()));
nodeFactory.markEndPosition();
expectToken(Token::RParen);
}
else
nodeFactory.setEndPositionFromNode(name);
- return nodeFactory.createNode<ModifierInvocation>(name, arguments);
+ return nodeFactory.createNode<ModifierInvocation>(name, move(arguments));
}
ASTPointer<Identifier> Parser::parseIdentifier()
@@ -776,6 +806,7 @@ ASTPointer<FunctionTypeName> Parser::parseFunctionType()
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
FunctionHeaderParserResult header = parseFunctionHeader(true, false);
+ solAssert(!header.isConstructor, "Tried to parse type as constructor.");
return nodeFactory.createNode<FunctionTypeName>(
header.parameters,
header.returnParameters,
diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h
index 3f780af9..eb120a61 100644
--- a/libsolidity/parsing/Parser.h
+++ b/libsolidity/parsing/Parser.h
@@ -56,6 +56,7 @@ private:
/// This struct is shared for parsing a function header and a function type.
struct FunctionHeaderParserResult
{
+ bool isConstructor;
ASTPointer<ASTString> name;
ASTPointer<ParameterList> parameters;
ASTPointer<ParameterList> returnParameters;
@@ -73,7 +74,11 @@ private:
ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier();
Declaration::Visibility parseVisibilitySpecifier(Token::Value _token);
StateMutability parseStateMutability(Token::Value _token);
- FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers);
+ FunctionHeaderParserResult parseFunctionHeader(
+ bool _forceEmptyName,
+ bool _allowModifiers,
+ ASTString const* _contractName = nullptr
+ );
ASTPointer<ASTNode> parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName);
ASTPointer<FunctionDefinition> parseFunctionDefinition(ASTString const* _contractName);
ASTPointer<StructDefinition> parseStructDefinition();
diff --git a/libsolidity/parsing/Token.cpp b/libsolidity/parsing/Token.cpp
index 9cec0303..5ce74316 100644
--- a/libsolidity/parsing/Token.cpp
+++ b/libsolidity/parsing/Token.cpp
@@ -53,7 +53,7 @@ namespace solidity
void ElementaryTypeNameToken::assertDetails(Token::Value _baseType, unsigned const& _first, unsigned const& _second)
{
- solAssert(Token::isElementaryTypeName(_baseType), "");
+ solAssert(Token::isElementaryTypeName(_baseType), "Expected elementary type name: " + string(Token::toString(_baseType)));
if (_baseType == Token::BytesM)
{
solAssert(_second == 0, "There should not be a second size argument to type bytesM.");