aboutsummaryrefslogtreecommitdiffstats
path: root/libsolidity
diff options
context:
space:
mode:
authorchriseth <chris@ethereum.org>2017-02-01 01:29:51 +0800
committerGitHub <noreply@github.com>2017-02-01 01:29:51 +0800
commit364da425d3116a4b85863df39a1864340861d71e (patch)
tree521a52c3f8182cd6a054f148bd649c8b6b0e2af9 /libsolidity
parent60cc1668517f56ce6ca8225555472e7a27eab8b0 (diff)
parent7b18c9df1dfa0076566bfa1e4a3bc5e5ba9c8594 (diff)
downloaddexon-solidity-364da425d3116a4b85863df39a1864340861d71e.tar.gz
dexon-solidity-364da425d3116a4b85863df39a1864340861d71e.tar.zst
dexon-solidity-364da425d3116a4b85863df39a1864340861d71e.zip
Merge pull request #1622 from ethereum/develop
Solidity version 0.4.9
Diffstat (limited to 'libsolidity')
-rw-r--r--libsolidity/analysis/DeclarationContainer.cpp22
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.cpp4
-rw-r--r--libsolidity/analysis/ReferencesResolver.cpp1
-rw-r--r--libsolidity/analysis/TypeChecker.cpp17
-rw-r--r--libsolidity/ast/AST.cpp44
-rw-r--r--libsolidity/ast/AST.h19
-rw-r--r--libsolidity/ast/ASTAnnotations.h2
-rw-r--r--libsolidity/ast/ASTJsonConverter.cpp4
-rw-r--r--libsolidity/ast/Types.cpp264
-rw-r--r--libsolidity/ast/Types.h50
-rw-r--r--libsolidity/codegen/ArrayUtils.cpp880
-rw-r--r--libsolidity/codegen/ArrayUtils.h5
-rw-r--r--libsolidity/codegen/CompilerContext.cpp79
-rw-r--r--libsolidity/codegen/CompilerContext.h47
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp75
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp20
-rw-r--r--libsolidity/codegen/ExpressionCompiler.cpp56
-rw-r--r--libsolidity/inlineasm/AsmParser.cpp16
-rw-r--r--libsolidity/inlineasm/AsmParser.h1
-rw-r--r--libsolidity/interface/CompilerStack.cpp118
-rw-r--r--libsolidity/interface/CompilerStack.h9
-rw-r--r--libsolidity/interface/InterfaceHandler.cpp48
-rw-r--r--libsolidity/interface/InterfaceHandler.h10
-rw-r--r--libsolidity/interface/Version.cpp4
-rw-r--r--libsolidity/interface/Version.h1
-rw-r--r--libsolidity/parsing/DocStringParser.cpp70
26 files changed, 1237 insertions, 629 deletions
diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp
index f8c12c5b..b33c8568 100644
--- a/libsolidity/analysis/DeclarationContainer.cpp
+++ b/libsolidity/analysis/DeclarationContainer.cpp
@@ -42,20 +42,32 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
if (m_invisibleDeclarations.count(*_name))
declarations += m_invisibleDeclarations.at(*_name);
- if (dynamic_cast<FunctionDefinition const*>(&_declaration))
+ if (
+ dynamic_cast<FunctionDefinition const*>(&_declaration) ||
+ dynamic_cast<EventDefinition const*>(&_declaration)
+ )
{
- // check that all other declarations with the same name are functions or a public state variable
+ // check that all other declarations with the same name are functions or a public state variable or events.
+ // And then check that the signatures are different.
for (Declaration const* declaration: declarations)
{
- if (dynamic_cast<FunctionDefinition const*>(declaration))
- continue;
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(declaration))
{
if (variableDeclaration->isStateVariable() && !variableDeclaration->isConstant() && variableDeclaration->isPublic())
continue;
return declaration;
}
- return declaration;
+ if (
+ dynamic_cast<FunctionDefinition const*>(&_declaration) &&
+ !dynamic_cast<FunctionDefinition const*>(declaration)
+ )
+ return declaration;
+ if (
+ dynamic_cast<EventDefinition const*>(&_declaration) &&
+ !dynamic_cast<EventDefinition const*>(declaration)
+ )
+ return declaration;
+ // Or, continue.
}
}
else if (declarations.size() == 1 && declarations.front() == &_declaration)
diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp
index 08323243..b0a82715 100644
--- a/libsolidity/analysis/NameAndTypeResolver.cpp
+++ b/libsolidity/analysis/NameAndTypeResolver.cpp
@@ -260,8 +260,8 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
for (auto it = _declarations.begin(); it != _declarations.end(); ++it)
{
solAssert(*it, "");
- // the declaration is functionDefinition or a VariableDeclaration while declarations > 1
- solAssert(dynamic_cast<FunctionDefinition const*>(*it) || dynamic_cast<VariableDeclaration const*>(*it),
+ // the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1
+ solAssert(dynamic_cast<FunctionDefinition const*>(*it) || dynamic_cast<EventDefinition const*>(*it) || dynamic_cast<VariableDeclaration const*>(*it),
"Found overloading involving something not a function or a variable");
shared_ptr<FunctionType const> functionType { (*it)->functionType(false) };
diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp
index 66bf1d0e..df579c3d 100644
--- a/libsolidity/analysis/ReferencesResolver.cpp
+++ b/libsolidity/analysis/ReferencesResolver.cpp
@@ -87,7 +87,6 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
{
switch (_typeName.visibility())
{
- case VariableDeclaration::Visibility::Default:
case VariableDeclaration::Visibility::Internal:
case VariableDeclaration::Visibility::External:
break;
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index 67c8ac17..533c787b 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -75,7 +75,10 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
checkContractAbstractConstructors(_contract);
FunctionDefinition const* function = _contract.constructor();
- if (function) {
+ if (function)
+ {
+ if (!function->isPublic())
+ _contract.annotation().hasPublicConstructor = false;
if (!function->returnParameters().empty())
typeError(function->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
if (function->isDeclaredConst())
@@ -1280,6 +1283,8 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
fatalTypeError(_newExpression.location(), "Identifier is not a contract.");
if (!contract->annotation().isFullyImplemented)
typeError(_newExpression.location(), "Trying to create an instance of an abstract contract.");
+ if (!contract->annotation().hasPublicConstructor)
+ typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly.");
solAssert(!!m_scope, "");
m_scope->annotation().contractDependencies.insert(contract);
@@ -1560,6 +1565,16 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
void TypeChecker::endVisit(Literal const& _literal)
{
+ if (_literal.looksLikeAddress())
+ {
+ if (_literal.passesAddressChecksum())
+ {
+ _literal.annotation().type = make_shared<IntegerType>(0, IntegerType::Modifier::Address);
+ return;
+ }
+ else
+ warning(_literal.location(), "This looks like an address but has an invalid checksum.");
+ }
_literal.annotation().type = Type::forLiteral(_literal);
if (!_literal.annotation().type)
fatalTypeError(_literal.location(), "Invalid literal value.");
diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp
index 6f7a64dc..616de54e 100644
--- a/libsolidity/ast/AST.cpp
+++ b/libsolidity/ast/AST.cpp
@@ -20,8 +20,6 @@
* Solidity abstract syntax tree.
*/
-#include <algorithm>
-#include <functional>
#include <libsolidity/interface/Utils.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h>
@@ -30,11 +28,31 @@
#include <libdevcore/SHA3.h>
+#include <boost/algorithm/string.hpp>
+
+#include <algorithm>
+#include <functional>
+
using namespace std;
using namespace dev;
using namespace dev::solidity;
+class IDDispenser
+{
+public:
+ static size_t next() { return ++instance(); }
+ static void reset() { instance() = 0; }
+private:
+ static size_t& instance()
+ {
+ static IDDispenser dispenser;
+ return dispenser.id;
+ }
+ size_t id = 0;
+};
+
ASTNode::ASTNode(SourceLocation const& _location):
+ m_id(IDDispenser::next()),
m_location(_location)
{
}
@@ -44,6 +62,11 @@ ASTNode::~ASTNode()
delete m_annotation;
}
+void ASTNode::resetID()
+{
+ IDDispenser::reset();
+}
+
ASTAnnotation& ASTNode::annotation() const
{
if (!m_annotation)
@@ -189,7 +212,6 @@ void ContractDefinition::setUserDocumentation(Json::Value const& _userDocumentat
m_userDocumentation = _userDocumentation;
}
-
vector<Declaration const*> const& ContractDefinition::inheritableMembers() const
{
if (!m_inheritableMembers)
@@ -503,3 +525,19 @@ IdentifierAnnotation& Identifier::annotation() const
m_annotation = new IdentifierAnnotation();
return static_cast<IdentifierAnnotation&>(*m_annotation);
}
+
+bool Literal::looksLikeAddress() const
+{
+ if (subDenomination() != SubDenomination::None)
+ return false;
+
+ string lit = value();
+ return lit.substr(0, 2) == "0x" && abs(int(lit.length()) - 42) <= 1;
+}
+
+bool Literal::passesAddressChecksum() const
+{
+ string lit = value();
+ solAssert(lit.substr(0, 2) == "0x", "Expected hex prefix");
+ return dev::passesAddressChecksum(lit, true);
+}
diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h
index 2d092408..743fdaa1 100644
--- a/libsolidity/ast/AST.h
+++ b/libsolidity/ast/AST.h
@@ -57,6 +57,11 @@ public:
explicit ASTNode(SourceLocation const& _location);
virtual ~ASTNode();
+ /// @returns an identifier of this AST node that is unique for a single compilation run.
+ size_t id() const { return m_id; }
+ /// Resets the global ID counter. This invalidates all previous IDs.
+ static void resetID();
+
virtual void accept(ASTVisitor& _visitor) = 0;
virtual void accept(ASTConstVisitor& _visitor) const = 0;
template <class T>
@@ -94,6 +99,7 @@ public:
///@}
protected:
+ size_t const m_id = 0;
/// Annotation - is specialised in derived classes, is created upon request (because of polymorphism).
mutable ASTAnnotation* m_annotation = nullptr;
@@ -161,6 +167,7 @@ public:
/// @returns the source name this declaration is present in.
/// Can be combined with annotation().canonicalName to form a globally unique name.
std::string sourceUnitName() const;
+ std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); }
virtual bool isLValue() const { return false; }
virtual bool isPartOfExternalInterface() const { return false; }
@@ -601,7 +608,7 @@ private:
/**
* Declaration of a variable. This can be used in various places, e.g. in function parameter
- * lists, struct definitions and even function bodys.
+ * lists, struct definitions and even function bodies.
*/
class VariableDeclaration: public Declaration
{
@@ -862,7 +869,10 @@ public:
std::vector<ASTPointer<VariableDeclaration>> const& parameterTypes() const { return m_parameterTypes->parameters(); }
std::vector<ASTPointer<VariableDeclaration>> const& returnParameterTypes() const { return m_returnTypes->parameters(); }
- Declaration::Visibility visibility() const { return m_visibility; }
+ Declaration::Visibility visibility() const
+ {
+ return m_visibility == Declaration::Visibility::Default ? Declaration::Visibility::Internal : m_visibility;
+ }
bool isDeclaredConst() const { return m_isDeclaredConst; }
bool isPayable() const { return m_isPayable; }
@@ -1574,6 +1584,11 @@ public:
SubDenomination subDenomination() const { return m_subDenomination; }
+ /// @returns true if this looks like a checksummed address.
+ bool looksLikeAddress() const;
+ /// @returns true if it passes the address checksum test.
+ bool passesAddressChecksum() const;
+
private:
Token::Value m_token;
ASTPointer<ASTString> m_value;
diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h
index 9c4c3ae8..61e97a55 100644
--- a/libsolidity/ast/ASTAnnotations.h
+++ b/libsolidity/ast/ASTAnnotations.h
@@ -80,6 +80,8 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnota
{
/// Whether all functions are implemented.
bool isFullyImplemented = true;
+ /// Whether a public constructor (even the default one) is available.
+ bool hasPublicConstructor = true;
/// List of all (direct and indirect) base contracts in order from derived to
/// base, including the contract itself.
std::vector<ContractDefinition const*> linearizedBaseContracts;
diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp
index abaad0fd..69c10c8d 100644
--- a/libsolidity/ast/ASTJsonConverter.cpp
+++ b/libsolidity/ast/ASTJsonConverter.cpp
@@ -42,7 +42,7 @@ void ASTJsonConverter::addJsonNode(
{
Json::Value node;
- node["id"] = reinterpret_cast<Json::UInt64>(&_node);
+ node["id"] = Json::UInt64(_node.id());
node["src"] = sourceLocationToString(_node.location());
node["name"] = _nodeName;
if (_attributes.size() != 0)
@@ -124,7 +124,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
{
Json::Value linearizedBaseContracts(Json::arrayValue);
for (auto const& baseContract: _node.annotation().linearizedBaseContracts)
- linearizedBaseContracts.append(reinterpret_cast<Json::UInt64>(baseContract));
+ linearizedBaseContracts.append(Json::UInt64(baseContract->id()));
addJsonNode(_node, "ContractDefinition", {
make_pair("name", _node.name()),
make_pair("isLibrary", _node.isLibrary()),
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp
index 03ff8471..dbabc8db 100644
--- a/libsolidity/ast/Types.cpp
+++ b/libsolidity/ast/Types.cpp
@@ -21,15 +21,22 @@
*/
#include <libsolidity/ast/Types.h>
-#include <limits>
-#include <boost/range/adaptor/reversed.hpp>
-#include <boost/range/adaptor/sliced.hpp>
+
+#include <libsolidity/interface/Utils.h>
+#include <libsolidity/ast/AST.h>
+
#include <libdevcore/CommonIO.h>
#include <libdevcore/CommonData.h>
#include <libdevcore/SHA3.h>
#include <libdevcore/UTF8.h>
-#include <libsolidity/interface/Utils.h>
-#include <libsolidity/ast/AST.h>
+
+#include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/range/adaptor/reversed.hpp>
+#include <boost/range/adaptor/sliced.hpp>
+#include <boost/range/adaptor/transformed.hpp>
+
+#include <limits>
using namespace std;
using namespace dev;
@@ -117,6 +124,51 @@ u256 const& MemberList::storageSize() const
return m_storageOffsets->storageSize();
}
+/// Helper functions for type identifier
+namespace
+{
+
+string parenthesizeIdentifier(string const& _internal)
+{
+ return "$_" + _internal + "_$";
+}
+
+template <class Range>
+string identifierList(Range const&& _list)
+{
+ return parenthesizeIdentifier(boost::algorithm::join(_list, "_$_"));
+}
+
+string identifier(TypePointer const& _type)
+{
+ return _type ? _type->identifier() : "";
+}
+
+string identifierList(vector<TypePointer> const& _list)
+{
+ return identifierList(_list | boost::adaptors::transformed(identifier));
+}
+
+string identifierList(TypePointer const& _type)
+{
+ return parenthesizeIdentifier(identifier(_type));
+}
+
+string identifierList(TypePointer const& _type1, TypePointer const& _type2)
+{
+ TypePointers list;
+ list.push_back(_type1);
+ list.push_back(_type2);
+ return identifierList(list);
+}
+
+string parenthesizeUserIdentifier(string const& _internal)
+{
+ return parenthesizeIdentifier(boost::algorithm::replace_all_copy(_internal, "$", "$$$"));
+}
+
+}
+
TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
{
solAssert(Token::isElementaryTypeName(_type.token()),
@@ -272,7 +324,15 @@ IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier):
solAssert(
m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0,
"Invalid bit number for integer type: " + dev::toString(_bits)
- );
+ );
+}
+
+string IntegerType::identifier() const
+{
+ if (isAddress())
+ return "t_address";
+ else
+ return "t_" + string(isSigned() ? "" : "u") + "int" + std::to_string(numBits());
}
bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
@@ -345,6 +405,14 @@ string IntegerType::toString(bool) const
return prefix + dev::toString(m_bits);
}
+u256 IntegerType::literalValue(Literal const* _literal) const
+{
+ solAssert(m_modifier == Modifier::Address, "");
+ solAssert(_literal, "");
+ solAssert(_literal->value().substr(0, 2) == "0x", "");
+ return u256(_literal->value());
+}
+
TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
{
if (
@@ -412,7 +480,12 @@ FixedPointType::FixedPointType(int _integerBits, int _fractionalBits, FixedPoint
m_fractionalBits % 8 == 0,
"Invalid bit number(s) for fixed type: " +
dev::toString(_integerBits) + "x" + dev::toString(_fractionalBits)
- );
+ );
+}
+
+string FixedPointType::identifier() const
+{
+ return "t_" + string(isSigned() ? "" : "u") + "fixed" + std::to_string(integerBits()) + "x" + std::to_string(fractionalBits());
}
bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const
@@ -770,6 +843,11 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
}
}
+string RationalNumberType::identifier() const
+{
+ return "t_rational_" + m_value.numerator().str() + "_by_" + m_value.denominator().str();
+}
+
bool RationalNumberType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -909,6 +987,13 @@ bool StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const
return false;
}
+string StringLiteralType::identifier() const
+{
+ // Since we have to return a valid identifier and the string itself may contain
+ // anything, we hash it.
+ return "t_stringliteral_" + toHex(keccak256(m_value).asBytes());
+}
+
bool StringLiteralType::operator==(const Type& _other) const
{
if (_other.category() != category())
@@ -1002,6 +1087,11 @@ MemberList::MemberMap FixedBytesType::nativeMembers(const ContractDefinition*) c
return MemberList::MemberMap{MemberList::Member{"length", make_shared<IntegerType>(8)}};
}
+string FixedBytesType::identifier() const
+{
+ return "t_bytes" + std::to_string(m_bytes);
+}
+
bool FixedBytesType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1115,6 +1205,20 @@ string ReferenceType::stringForReferencePart() const
return "";
}
+string ReferenceType::identifierLocationSuffix() const
+{
+ string id;
+ if (location() == DataLocation::Storage)
+ id += "_storage";
+ else if (location() == DataLocation::Memory)
+ id += "_memory";
+ else
+ id += "_calldata";
+ if (isPointer())
+ id += "_ptr";
+ return id;
+}
+
bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const
{
if (_convertTo.category() != category())
@@ -1170,6 +1274,27 @@ bool ArrayType::isExplicitlyConvertibleTo(const Type& _convertTo) const
return true;
}
+string ArrayType::identifier() const
+{
+ string id;
+ if (isString())
+ id = "t_string";
+ else if (isByteArray())
+ id = "t_bytes";
+ else
+ {
+ id = "t_array";
+ id += identifierList(baseType());
+ if (isDynamicallySized())
+ id += "dyn";
+ else
+ id += length().str();
+ }
+ id += identifierLocationSuffix();
+
+ return id;
+}
+
bool ArrayType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1184,7 +1309,7 @@ bool ArrayType::operator==(Type const& _other) const
return false;
if (*other.baseType() != *baseType())
return false;
- return isDynamicallySized() || length() == other.length();
+ return isDynamicallySized() || length() == other.length();
}
unsigned ArrayType::calldataEncodedSize(bool _padded) const
@@ -1356,6 +1481,11 @@ TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer)
return copy;
}
+string ContractType::identifier() const
+{
+ return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + std::to_string(m_contract.id());
+}
+
bool ContractType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1465,6 +1595,11 @@ bool StructType::isImplicitlyConvertibleTo(const Type& _convertTo) const
return this->m_struct == convertTo.m_struct;
}
+string StructType::identifier() const
+{
+ return "t_struct" + parenthesizeUserIdentifier(m_struct.name()) + std::to_string(m_struct.id()) + identifierLocationSuffix();
+}
+
bool StructType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1605,6 +1740,11 @@ TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const
return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer();
}
+string EnumType::identifier() const
+{
+ return "t_enum" + parenthesizeUserIdentifier(m_enum.name()) + std::to_string(m_enum.id());
+}
+
bool EnumType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1686,6 +1826,11 @@ bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const
return false;
}
+string TupleType::identifier() const
+{
+ return "t_tuple" + identifierList(components());
+}
+
bool TupleType::operator==(Type const& _other) const
{
if (auto tupleType = dynamic_cast<TupleType const*>(&_other))
@@ -1934,6 +2079,53 @@ TypePointers FunctionType::parameterTypes() const
return TypePointers(m_parameterTypes.cbegin() + 1, m_parameterTypes.cend());
}
+string FunctionType::identifier() const
+{
+ string id = "t_function_";
+ switch (location())
+ {
+ case Location::Internal: id += "internal"; break;
+ case Location::External: id += "external"; break;
+ case Location::CallCode: id += "callcode"; break;
+ case Location::DelegateCall: id += "delegatecall"; break;
+ case Location::Bare: id += "bare"; break;
+ case Location::BareCallCode: id += "barecallcode"; break;
+ case Location::BareDelegateCall: id += "baredelegatecall"; break;
+ case Location::Creation: id += "creation"; break;
+ case Location::Send: id += "send"; break;
+ case Location::SHA3: id += "sha3"; break;
+ case Location::Selfdestruct: id += "selfdestruct"; break;
+ case Location::ECRecover: id += "ecrecover"; break;
+ case Location::SHA256: id += "sha256"; break;
+ case Location::RIPEMD160: id += "ripemd160"; break;
+ case Location::Log0: id += "log0"; break;
+ case Location::Log1: id += "log1"; break;
+ case Location::Log2: id += "log2"; break;
+ case Location::Log3: id += "log3"; break;
+ case Location::Log4: id += "log4"; break;
+ case Location::Event: id += "event"; break;
+ case Location::SetGas: id += "setgas"; break;
+ case Location::SetValue: id += "setvalue"; break;
+ case Location::BlockHash: id += "blockhash"; break;
+ case Location::AddMod: id += "addmod"; break;
+ case Location::MulMod: id += "mulmod"; break;
+ case Location::ArrayPush: id += "arraypush"; break;
+ case Location::ByteArrayPush: id += "bytearraypush"; break;
+ case Location::ObjectCreation: id += "objectcreation"; break;
+ default: solAssert(false, "Unknown function location."); break;
+ }
+ if (isConstant())
+ id += "_constant";
+ id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes);
+ if (m_gasSet)
+ id += "gas";
+ if (m_valueSet)
+ id += "value";
+ if (bound())
+ id += "bound_to" + identifierList(selfType());
+ return id;
+}
+
bool FunctionType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2320,25 +2512,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
);
}
-vector<string> const FunctionType::parameterTypeNames(bool _addDataLocation) const
-{
- vector<string> names;
- for (TypePointer const& t: parameterTypes())
- names.push_back(t->canonicalName(_addDataLocation));
-
- return names;
-}
-
-vector<string> const FunctionType::returnParameterTypeNames(bool _addDataLocation) const
-{
- vector<string> names;
- for (TypePointer const& t: m_returnParameterTypes)
- names.push_back(t->canonicalName(_addDataLocation));
-
- return names;
-}
-
-TypePointer FunctionType::selfType() const
+TypePointer const& FunctionType::selfType() const
{
solAssert(bound(), "Function is not bound.");
solAssert(m_parameterTypes.size() > 0, "Function has no self type.");
@@ -2354,6 +2528,11 @@ ASTPointer<ASTString> FunctionType::documentation() const
return ASTPointer<ASTString>();
}
+string MappingType::identifier() const
+{
+ return "t_mapping" + identifierList(m_keyType, m_valueType);
+}
+
bool MappingType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2372,6 +2551,11 @@ string MappingType::canonicalName(bool) const
return "mapping(" + keyType()->canonicalName(false) + " => " + valueType()->canonicalName(false) + ")";
}
+string TypeType::identifier() const
+{
+ return "t_type" + identifierList(actualType());
+}
+
bool TypeType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2456,6 +2640,11 @@ u256 ModifierType::storageSize() const
<< errinfo_comment("Storage size of non-storable type type requested."));
}
+string ModifierType::identifier() const
+{
+ return "t_modifier" + identifierList(m_parameterTypes);
+}
+
bool ModifierType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2480,6 +2669,11 @@ string ModifierType::toString(bool _short) const
return name + ")";
}
+string ModuleType::identifier() const
+{
+ return "t_module_" + std::to_string(m_sourceUnit.id());
+}
+
bool ModuleType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2501,6 +2695,22 @@ string ModuleType::toString(bool) const
return string("module \"") + m_sourceUnit.annotation().path + string("\"");
}
+string MagicType::identifier() const
+{
+ switch (m_kind)
+ {
+ case Kind::Block:
+ return "t_magic_block";
+ case Kind::Message:
+ return "t_magic_message";
+ case Kind::Transaction:
+ return "t_magic_transaction";
+ default:
+ solAssert(false, "Unknown kind of magic");
+ }
+ return "";
+}
+
bool MagicType::operator==(Type const& _other) const
{
if (_other.category() != category())
diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h
index 26e2b8f2..a5147f17 100644
--- a/libsolidity/ast/Types.h
+++ b/libsolidity/ast/Types.h
@@ -22,18 +22,21 @@
#pragma once
-#include <memory>
-#include <string>
-#include <map>
-#include <boost/noncopyable.hpp>
-#include <boost/rational.hpp>
-#include <libdevcore/Common.h>
-#include <libdevcore/CommonIO.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/ast/ASTForward.h>
#include <libsolidity/parsing/Token.h>
+
+#include <libdevcore/Common.h>
+#include <libdevcore/CommonIO.h>
#include <libdevcore/UndefMacros.h>
+#include <boost/noncopyable.hpp>
+#include <boost/rational.hpp>
+
+#include <memory>
+#include <string>
+#include <map>
+
namespace dev
{
namespace solidity
@@ -155,6 +158,13 @@ public:
static TypePointer commonType(TypePointer const& _a, TypePointer const& _b);
virtual Category category() const = 0;
+ /// @returns a valid solidity identifier such that two types should compare equal if and
+ /// only if they have the same identifier.
+ /// The identifier should start with "t_".
+ /// More complex identifier strings use "parentheses", where $_ is interpreted as as
+ /// "opening parenthesis", _$ as "closing parenthesis", _$_ as "comma" and any $ that
+ /// appears as part of a user-supplied identifier is escaped as _$$$_.
+ virtual std::string identifier() const = 0;
virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; }
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const
{
@@ -288,6 +298,7 @@ public:
explicit IntegerType(int _bits, Modifier _modifier = Modifier::Unsigned);
+ virtual std::string identifier() const override;
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
@@ -303,6 +314,8 @@ public:
virtual std::string toString(bool _short) const override;
+ virtual u256 literalValue(Literal const* _literal) const override;
+
virtual TypePointer encodingType() const override { return shared_from_this(); }
virtual TypePointer interfaceType(bool) const override { return shared_from_this(); }
@@ -329,6 +342,7 @@ public:
explicit FixedPointType(int _integerBits, int _fractionalBits, Modifier _modifier = Modifier::Unsigned);
+ virtual std::string identifier() const override;
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
@@ -378,6 +392,7 @@ public:
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
@@ -416,6 +431,7 @@ public:
return TypePointer();
}
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
@@ -449,6 +465,7 @@ public:
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
@@ -476,6 +493,7 @@ class BoolType: public Type
public:
BoolType() {}
virtual Category category() const override { return Category::Bool; }
+ virtual std::string identifier() const override { return "t_bool"; }
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
@@ -533,6 +551,8 @@ protected:
TypePointer copyForLocationIfReference(TypePointer const& _type) const;
/// @returns a human-readable description of the reference part of the type.
std::string stringForReferencePart() const;
+ /// @returns the suffix computed from the reference part to be used by identifier();
+ std::string identifierLocationSuffix() const;
DataLocation m_location = DataLocation::Storage;
bool m_isPointer = true;
@@ -573,6 +593,7 @@ public:
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(const Type& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded) const override;
virtual bool isDynamicallySized() const override { return m_hasDynamicLength; }
@@ -622,6 +643,7 @@ public:
/// Contracts can be converted to themselves and to integers.
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded ) const override
{
@@ -677,6 +699,7 @@ public:
explicit StructType(StructDefinition const& _struct, DataLocation _location = DataLocation::Storage):
ReferenceType(_location), m_struct(_struct) {}
virtual bool isImplicitlyConvertibleTo(const Type& _convertTo) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded) const override;
u256 memorySize() const;
@@ -720,6 +743,7 @@ public:
virtual Category category() const override { return Category::Enum; }
explicit EnumType(EnumDefinition const& _enum): m_enum(_enum) {}
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded) const override
{
@@ -760,6 +784,7 @@ public:
virtual Category category() const override { return Category::Tuple; }
explicit TupleType(std::vector<TypePointer> const& _types = std::vector<TypePointer>()): m_components(_types) {}
virtual bool isImplicitlyConvertibleTo(Type const& _other) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
virtual std::string toString(bool) const override;
@@ -890,13 +915,12 @@ public:
TypePointers parameterTypes() const;
std::vector<std::string> parameterNames() const;
- std::vector<std::string> const parameterTypeNames(bool _addDataLocation) const;
TypePointers const& returnParameterTypes() const { return m_returnParameterTypes; }
std::vector<std::string> const& returnParameterNames() const { return m_returnParameterNames; }
- std::vector<std::string> const returnParameterTypeNames(bool _addDataLocation) const;
/// @returns the "self" parameter type for a bound function
- TypePointer selfType() const;
+ TypePointer const& selfType() const;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual std::string canonicalName(bool /*_addDataLocation*/) const override;
@@ -995,6 +1019,7 @@ public:
MappingType(TypePointer const& _keyType, TypePointer const& _valueType):
m_keyType(_keyType), m_valueType(_valueType) {}
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual std::string toString(bool _short) const override;
virtual std::string canonicalName(bool _addDataLocation) const override;
@@ -1029,6 +1054,7 @@ public:
TypePointer const& actualType() const { return m_actualType; }
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
virtual u256 storageSize() const override;
@@ -1056,6 +1082,7 @@ public:
virtual u256 storageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned sizeOnStack() const override { return 0; }
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual std::string toString(bool _short) const override;
@@ -1080,6 +1107,7 @@ public:
return TypePointer();
}
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
virtual bool canLiveOutsideStorage() const override { return true; }
@@ -1109,6 +1137,7 @@ public:
return TypePointer();
}
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
virtual bool canLiveOutsideStorage() const override { return true; }
@@ -1132,6 +1161,7 @@ class InaccessibleDynamicType: public Type
public:
virtual Category category() const override { return Category::InaccessibleDynamic; }
+ virtual std::string identifier() const override { return "t_inaccessible"; }
virtual bool isImplicitlyConvertibleTo(Type const&) const override { return false; }
virtual bool isExplicitlyConvertibleTo(Type const&) const override { return false; }
virtual unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; }
diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp
index 352c7177..bdd29abd 100644
--- a/libsolidity/codegen/ArrayUtils.cpp
+++ b/libsolidity/codegen/ArrayUtils.cpp
@@ -40,9 +40,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
// stack layout: [source_ref] [source length] target_ref (top)
solAssert(_targetType.location() == DataLocation::Storage, "");
- IntegerType uint256(256);
- Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.baseType());
- Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.baseType());
+ TypePointer uint256 = make_shared<IntegerType>(256);
+ TypePointer targetBaseType = _targetType.isByteArray() ? uint256 : _targetType.baseType();
+ TypePointer sourceBaseType = _sourceType.isByteArray() ? uint256 : _sourceType.baseType();
// TODO unroll loop for small sizes
@@ -70,202 +70,216 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
}
// stack: target_ref source_ref source_length
- m_context << Instruction::DUP3;
- // stack: target_ref source_ref source_length target_ref
- retrieveLength(_targetType);
- // stack: target_ref source_ref source_length target_ref target_length
- if (_targetType.isDynamicallySized())
- // store new target length
- if (!_targetType.isByteArray())
- // Otherwise, length will be stored below.
- m_context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE;
- if (sourceBaseType->category() == Type::Category::Mapping)
- {
- solAssert(targetBaseType->category() == Type::Category::Mapping, "");
- solAssert(_sourceType.location() == DataLocation::Storage, "");
- // nothing to copy
- m_context
- << Instruction::POP << Instruction::POP
- << Instruction::POP << Instruction::POP;
- return;
- }
- // stack: target_ref source_ref source_length target_ref target_length
- // compute hashes (data positions)
- m_context << Instruction::SWAP1;
- if (_targetType.isDynamicallySized())
- CompilerUtils(m_context).computeHashStatic();
- // stack: target_ref source_ref source_length target_length target_data_pos
- m_context << Instruction::SWAP1;
- convertLengthToSize(_targetType);
- m_context << Instruction::DUP2 << Instruction::ADD;
- // stack: target_ref source_ref source_length target_data_pos target_data_end
- m_context << Instruction::SWAP3;
- // stack: target_ref target_data_end source_length target_data_pos source_ref
-
- eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag();
-
- // special case for short byte arrays: Store them together with their length.
- if (_targetType.isByteArray())
- {
- // stack: target_ref target_data_end source_length target_data_pos source_ref
- m_context << Instruction::DUP3 << u256(31) << Instruction::LT;
- eth::AssemblyItem longByteArray = m_context.appendConditionalJump();
- // store the short byte array
- solAssert(_sourceType.isByteArray(), "");
- if (_sourceType.location() == DataLocation::Storage)
- {
- // just copy the slot, it contains length and data
- m_context << Instruction::DUP1 << Instruction::SLOAD;
- m_context << Instruction::DUP6 << Instruction::SSTORE;
- }
- else
+ TypePointer targetType = _targetType.shared_from_this();
+ TypePointer sourceType = _sourceType.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(),
+ 3,
+ 1,
+ [=](CompilerContext& _context)
{
- m_context << Instruction::DUP1;
- CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
- // stack: target_ref target_data_end source_length target_data_pos source_ref value
- // clear the lower-order byte - which will hold the length
- m_context << u256(0xff) << Instruction::NOT << Instruction::AND;
- // fetch the length and shift it left by one
- m_context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD;
- // combine value and length and store them
- m_context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE;
- }
- // end of special case, jump right into cleaning target data area
- m_context.appendJumpTo(copyLoopEndWithoutByteOffset);
- m_context << longByteArray;
- // Store length (2*length+1)
- m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
- m_context << u256(1) << Instruction::ADD;
- m_context << Instruction::DUP6 << Instruction::SSTORE;
- }
+ ArrayUtils utils(_context);
+ ArrayType const& _sourceType = dynamic_cast<ArrayType const&>(*sourceType);
+ ArrayType const& _targetType = dynamic_cast<ArrayType const&>(*targetType);
+ // stack: target_ref source_ref source_length
+ _context << Instruction::DUP3;
+ // stack: target_ref source_ref source_length target_ref
+ utils.retrieveLength(_targetType);
+ // stack: target_ref source_ref source_length target_ref target_length
+ if (_targetType.isDynamicallySized())
+ // store new target length
+ if (!_targetType.isByteArray())
+ // Otherwise, length will be stored below.
+ _context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE;
+ if (sourceBaseType->category() == Type::Category::Mapping)
+ {
+ solAssert(targetBaseType->category() == Type::Category::Mapping, "");
+ solAssert(_sourceType.location() == DataLocation::Storage, "");
+ // nothing to copy
+ _context
+ << Instruction::POP << Instruction::POP
+ << Instruction::POP << Instruction::POP;
+ return;
+ }
+ // stack: target_ref source_ref source_length target_ref target_length
+ // compute hashes (data positions)
+ _context << Instruction::SWAP1;
+ if (_targetType.isDynamicallySized())
+ CompilerUtils(_context).computeHashStatic();
+ // stack: target_ref source_ref source_length target_length target_data_pos
+ _context << Instruction::SWAP1;
+ utils.convertLengthToSize(_targetType);
+ _context << Instruction::DUP2 << Instruction::ADD;
+ // stack: target_ref source_ref source_length target_data_pos target_data_end
+ _context << Instruction::SWAP3;
+ // stack: target_ref target_data_end source_length target_data_pos source_ref
+
+ eth::AssemblyItem copyLoopEndWithoutByteOffset = _context.newTag();
+
+ // special case for short byte arrays: Store them together with their length.
+ if (_targetType.isByteArray())
+ {
+ // stack: target_ref target_data_end source_length target_data_pos source_ref
+ _context << Instruction::DUP3 << u256(31) << Instruction::LT;
+ eth::AssemblyItem longByteArray = _context.appendConditionalJump();
+ // store the short byte array
+ solAssert(_sourceType.isByteArray(), "");
+ if (_sourceType.location() == DataLocation::Storage)
+ {
+ // just copy the slot, it contains length and data
+ _context << Instruction::DUP1 << Instruction::SLOAD;
+ _context << Instruction::DUP6 << Instruction::SSTORE;
+ }
+ else
+ {
+ _context << Instruction::DUP1;
+ CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
+ // stack: target_ref target_data_end source_length target_data_pos source_ref value
+ // clear the lower-order byte - which will hold the length
+ _context << u256(0xff) << Instruction::NOT << Instruction::AND;
+ // fetch the length and shift it left by one
+ _context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD;
+ // combine value and length and store them
+ _context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE;
+ }
+ // end of special case, jump right into cleaning target data area
+ _context.appendJumpTo(copyLoopEndWithoutByteOffset);
+ _context << longByteArray;
+ // Store length (2*length+1)
+ _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
+ _context << u256(1) << Instruction::ADD;
+ _context << Instruction::DUP6 << Instruction::SSTORE;
+ }
- // skip copying if source length is zero
- m_context << Instruction::DUP3 << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
-
- if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized())
- CompilerUtils(m_context).computeHashStatic();
- // stack: target_ref target_data_end source_length target_data_pos source_data_pos
- m_context << Instruction::SWAP2;
- convertLengthToSize(_sourceType);
- m_context << Instruction::DUP3 << Instruction::ADD;
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end
- if (haveByteOffsetTarget)
- m_context << u256(0);
- if (haveByteOffsetSource)
- m_context << u256(0);
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
- eth::AssemblyItem copyLoopStart = m_context.newTag();
- m_context << copyLoopStart;
- // check for loop condition
- m_context
- << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize)
- << Instruction::GT << Instruction::ISZERO;
- eth::AssemblyItem copyLoopEnd = m_context.appendConditionalJump();
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
- // copy
- if (sourceBaseType->category() == Type::Category::Array)
- {
- solAssert(byteOffsetSize == 0, "Byte offset for array as base type.");
- auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType);
- m_context << Instruction::DUP3;
- if (sourceBaseArrayType.location() == DataLocation::Memory)
- m_context << Instruction::MLOAD;
- m_context << Instruction::DUP3;
- copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType);
- m_context << Instruction::POP;
- }
- else if (directCopy)
- {
- solAssert(byteOffsetSize == 0, "Byte offset for direct copy.");
- m_context
- << Instruction::DUP3 << Instruction::SLOAD
- << Instruction::DUP3 << Instruction::SSTORE;
- }
- else
- {
- // Note that we have to copy each element on its own in case conversion is involved.
- // We might copy too much if there is padding at the last element, but this way end
- // checking is easier.
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
- m_context << dupInstruction(3 + byteOffsetSize);
- if (_sourceType.location() == DataLocation::Storage)
- {
+ // skip copying if source length is zero
+ _context << Instruction::DUP3 << Instruction::ISZERO;
+ _context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
+
+ if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized())
+ CompilerUtils(_context).computeHashStatic();
+ // stack: target_ref target_data_end source_length target_data_pos source_data_pos
+ _context << Instruction::SWAP2;
+ utils.convertLengthToSize(_sourceType);
+ _context << Instruction::DUP3 << Instruction::ADD;
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end
+ if (haveByteOffsetTarget)
+ _context << u256(0);
if (haveByteOffsetSource)
- m_context << Instruction::DUP2;
+ _context << u256(0);
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
+ eth::AssemblyItem copyLoopStart = _context.newTag();
+ _context << copyLoopStart;
+ // check for loop condition
+ _context
+ << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize)
+ << Instruction::GT << Instruction::ISZERO;
+ eth::AssemblyItem copyLoopEnd = _context.appendConditionalJump();
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
+ // copy
+ if (sourceBaseType->category() == Type::Category::Array)
+ {
+ solAssert(byteOffsetSize == 0, "Byte offset for array as base type.");
+ auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType);
+ _context << Instruction::DUP3;
+ if (sourceBaseArrayType.location() == DataLocation::Memory)
+ _context << Instruction::MLOAD;
+ _context << Instruction::DUP3;
+ utils.copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType);
+ _context << Instruction::POP;
+ }
+ else if (directCopy)
+ {
+ solAssert(byteOffsetSize == 0, "Byte offset for direct copy.");
+ _context
+ << Instruction::DUP3 << Instruction::SLOAD
+ << Instruction::DUP3 << Instruction::SSTORE;
+ }
+ else
+ {
+ // Note that we have to copy each element on its own in case conversion is involved.
+ // We might copy too much if there is padding at the last element, but this way end
+ // checking is easier.
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
+ _context << dupInstruction(3 + byteOffsetSize);
+ if (_sourceType.location() == DataLocation::Storage)
+ {
+ if (haveByteOffsetSource)
+ _context << Instruction::DUP2;
+ else
+ _context << u256(0);
+ StorageItem(_context, *sourceBaseType).retrieveValue(SourceLocation(), true);
+ }
+ else if (sourceBaseType->isValueType())
+ CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
+ else
+ solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported.");
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
+ solAssert(
+ 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
+ "Stack too deep, try removing local variables."
+ );
+ // fetch target storage reference
+ _context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack());
+ if (haveByteOffsetTarget)
+ _context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack());
+ else
+ _context << u256(0);
+ StorageItem(_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true);
+ }
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
+ // increment source
+ if (haveByteOffsetSource)
+ utils.incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4);
else
- m_context << u256(0);
- StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true);
+ {
+ _context << swapInstruction(2 + byteOffsetSize);
+ if (sourceIsStorage)
+ _context << sourceBaseType->storageSize();
+ else if (_sourceType.location() == DataLocation::Memory)
+ _context << sourceBaseType->memoryHeadSize();
+ else
+ _context << sourceBaseType->calldataEncodedSize(true);
+ _context
+ << Instruction::ADD
+ << swapInstruction(2 + byteOffsetSize);
+ }
+ // increment target
+ if (haveByteOffsetTarget)
+ utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
+ else
+ _context
+ << swapInstruction(1 + byteOffsetSize)
+ << targetBaseType->storageSize()
+ << Instruction::ADD
+ << swapInstruction(1 + byteOffsetSize);
+ _context.appendJumpTo(copyLoopStart);
+ _context << copyLoopEnd;
+ if (haveByteOffsetTarget)
+ {
+ // clear elements that might be left over in the current slot in target
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset]
+ _context << dupInstruction(byteOffsetSize) << Instruction::ISZERO;
+ eth::AssemblyItem copyCleanupLoopEnd = _context.appendConditionalJump();
+ _context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize);
+ StorageItem(_context, *targetBaseType).setToZero(SourceLocation(), true);
+ utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
+ _context.appendJumpTo(copyLoopEnd);
+
+ _context << copyCleanupLoopEnd;
+ _context << Instruction::POP; // might pop the source, but then target is popped next
+ }
+ if (haveByteOffsetSource)
+ _context << Instruction::POP;
+ _context << copyLoopEndWithoutByteOffset;
+
+ // zero-out leftovers in target
+ // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end
+ _context << Instruction::POP << Instruction::SWAP1 << Instruction::POP;
+ // stack: target_ref target_data_end target_data_pos_updated
+ utils.clearStorageLoop(targetBaseType);
+ _context << Instruction::POP;
}
- else if (sourceBaseType->isValueType())
- CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
- else
- solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported.");
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
- solAssert(
- 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
- "Stack too deep, try removing local variables."
- );
- // fetch target storage reference
- m_context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack());
- if (haveByteOffsetTarget)
- m_context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack());
- else
- m_context << u256(0);
- StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true);
- }
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
- // increment source
- if (haveByteOffsetSource)
- incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4);
- else
- {
- m_context << swapInstruction(2 + byteOffsetSize);
- if (sourceIsStorage)
- m_context << sourceBaseType->storageSize();
- else if (_sourceType.location() == DataLocation::Memory)
- m_context << sourceBaseType->memoryHeadSize();
- else
- m_context << sourceBaseType->calldataEncodedSize(true);
- m_context
- << Instruction::ADD
- << swapInstruction(2 + byteOffsetSize);
- }
- // increment target
- if (haveByteOffsetTarget)
- incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
- else
- m_context
- << swapInstruction(1 + byteOffsetSize)
- << targetBaseType->storageSize()
- << Instruction::ADD
- << swapInstruction(1 + byteOffsetSize);
- m_context.appendJumpTo(copyLoopStart);
- m_context << copyLoopEnd;
- if (haveByteOffsetTarget)
- {
- // clear elements that might be left over in the current slot in target
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset]
- m_context << dupInstruction(byteOffsetSize) << Instruction::ISZERO;
- eth::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump();
- m_context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize);
- StorageItem(m_context, *targetBaseType).setToZero(SourceLocation(), true);
- incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
- m_context.appendJumpTo(copyLoopEnd);
-
- m_context << copyCleanupLoopEnd;
- m_context << Instruction::POP; // might pop the source, but then target is popped next
- }
- if (haveByteOffsetSource)
- m_context << Instruction::POP;
- m_context << copyLoopEndWithoutByteOffset;
-
- // zero-out leftovers in target
- // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end
- m_context << Instruction::POP << Instruction::SWAP1 << Instruction::POP;
- // stack: target_ref target_data_end target_data_pos_updated
- clearStorageLoop(*targetBaseType);
- m_context << Instruction::POP;
+ );
}
void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const
@@ -502,60 +516,70 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
}
}
-void ArrayUtils::clearArray(ArrayType const& _type) const
+void ArrayUtils::clearArray(ArrayType const& _typeIn) const
{
- unsigned stackHeightStart = m_context.stackHeight();
- solAssert(_type.location() == DataLocation::Storage, "");
- if (_type.baseType()->storageBytes() < 32)
- {
- solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
- solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type.");
- }
- if (_type.baseType()->isValueType())
- solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type.");
-
- m_context << Instruction::POP; // remove byte offset
- if (_type.isDynamicallySized())
- clearDynamicArray(_type);
- else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping)
- m_context << Instruction::POP;
- else if (_type.baseType()->isValueType() && _type.storageSize() <= 5)
- {
- // unroll loop for small arrays @todo choose a good value
- // Note that we loop over storage slots here, not elements.
- for (unsigned i = 1; i < _type.storageSize(); ++i)
- m_context
- << u256(0) << Instruction::DUP2 << Instruction::SSTORE
- << u256(1) << Instruction::ADD;
- m_context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE;
- }
- else if (!_type.baseType()->isValueType() && _type.length() <= 4)
- {
- // unroll loop for small arrays @todo choose a good value
- solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size.");
- for (unsigned i = 1; i < _type.length(); ++i)
+ TypePointer type = _typeIn.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$clearArray_" + _typeIn.identifier(),
+ 2,
+ 0,
+ [type](CompilerContext& _context)
{
- m_context << u256(0);
- StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), false);
- m_context
- << Instruction::POP
- << u256(_type.baseType()->storageSize()) << Instruction::ADD;
+ ArrayType const& _type = dynamic_cast<ArrayType const&>(*type);
+ unsigned stackHeightStart = _context.stackHeight();
+ solAssert(_type.location() == DataLocation::Storage, "");
+ if (_type.baseType()->storageBytes() < 32)
+ {
+ solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
+ solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type.");
+ }
+ if (_type.baseType()->isValueType())
+ solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type.");
+
+ _context << Instruction::POP; // remove byte offset
+ if (_type.isDynamicallySized())
+ ArrayUtils(_context).clearDynamicArray(_type);
+ else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping)
+ _context << Instruction::POP;
+ else if (_type.baseType()->isValueType() && _type.storageSize() <= 5)
+ {
+ // unroll loop for small arrays @todo choose a good value
+ // Note that we loop over storage slots here, not elements.
+ for (unsigned i = 1; i < _type.storageSize(); ++i)
+ _context
+ << u256(0) << Instruction::DUP2 << Instruction::SSTORE
+ << u256(1) << Instruction::ADD;
+ _context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE;
+ }
+ else if (!_type.baseType()->isValueType() && _type.length() <= 4)
+ {
+ // unroll loop for small arrays @todo choose a good value
+ solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size.");
+ for (unsigned i = 1; i < _type.length(); ++i)
+ {
+ _context << u256(0);
+ StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), false);
+ _context
+ << Instruction::POP
+ << u256(_type.baseType()->storageSize()) << Instruction::ADD;
+ }
+ _context << u256(0);
+ StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), true);
+ }
+ else
+ {
+ _context << Instruction::DUP1 << _type.length();
+ ArrayUtils(_context).convertLengthToSize(_type);
+ _context << Instruction::ADD << Instruction::SWAP1;
+ if (_type.baseType()->storageBytes() < 32)
+ ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
+ else
+ ArrayUtils(_context).clearStorageLoop(_type.baseType());
+ _context << Instruction::POP;
+ }
+ solAssert(_context.stackHeight() == stackHeightStart - 2, "");
}
- m_context << u256(0);
- StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true);
- }
- else
- {
- m_context << Instruction::DUP1 << _type.length();
- convertLengthToSize(_type);
- m_context << Instruction::ADD << Instruction::SWAP1;
- if (_type.baseType()->storageBytes() < 32)
- clearStorageLoop(IntegerType(256));
- else
- clearStorageLoop(*_type.baseType());
- m_context << Instruction::POP;
- }
- solAssert(m_context.stackHeight() == stackHeightStart - 2, "");
+ );
}
void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
@@ -589,191 +613,209 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
<< Instruction::SWAP1;
// stack: data_pos_end data_pos
if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
- clearStorageLoop(IntegerType(256));
+ clearStorageLoop(make_shared<IntegerType>(256));
else
- clearStorageLoop(*_type.baseType());
+ clearStorageLoop(_type.baseType());
// cleanup
m_context << endTag;
m_context << Instruction::POP;
}
-void ArrayUtils::resizeDynamicArray(ArrayType const& _type) const
+void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) 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.");
-
- unsigned stackHeightStart = m_context.stackHeight();
- eth::AssemblyItem resizeEnd = m_context.newTag();
-
- // stack: ref new_length
- // fetch old length
- retrieveLength(_type, 1);
- // stack: ref new_length old_length
- solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "2");
-
- // Special case for short byte arrays, they are stored together with their length
- if (_type.isByteArray())
- {
- eth::AssemblyItem regularPath = m_context.newTag();
- // We start by a large case-distinction about the old and new length of the byte array.
-
- m_context << Instruction::DUP3 << Instruction::SLOAD;
- // stack: ref new_length current_length ref_value
-
- solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
- m_context << Instruction::DUP2 << u256(31) << Instruction::LT;
- eth::AssemblyItem currentIsLong = m_context.appendConditionalJump();
- m_context << Instruction::DUP3 << u256(31) << Instruction::LT;
- eth::AssemblyItem newIsLong = m_context.appendConditionalJump();
-
- // Here: short -> short
-
- // Compute 1 << (256 - 8 * new_size)
- eth::AssemblyItem shortToShort = m_context.newTag();
- m_context << shortToShort;
- m_context << Instruction::DUP3 << u256(8) << Instruction::MUL;
- m_context << u256(0x100) << Instruction::SUB;
- m_context << u256(2) << Instruction::EXP;
- // Divide and multiply by that value, clearing bits.
- m_context << Instruction::DUP1 << Instruction::SWAP2;
- m_context << Instruction::DIV << Instruction::MUL;
- // Insert 2*length.
- m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
- m_context << Instruction::OR;
- // Store.
- m_context << Instruction::DUP4 << Instruction::SSTORE;
- solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3");
- m_context.appendJumpTo(resizeEnd);
-
- m_context.adjustStackOffset(1); // we have to do that because of the jumps
- // Here: short -> long
-
- m_context << newIsLong;
- // stack: ref new_length current_length ref_value
- solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
- // Zero out lower-order byte.
- m_context << u256(0xff) << Instruction::NOT << Instruction::AND;
- // Store at data location.
- m_context << Instruction::DUP4;
- CompilerUtils(m_context).computeHashStatic();
- m_context << Instruction::SSTORE;
- // stack: ref new_length current_length
- // Store new length: Compule 2*length + 1 and store it.
- m_context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD;
- m_context << u256(1) << Instruction::ADD;
- // stack: ref new_length current_length 2*new_length+1
- m_context << Instruction::DUP4 << Instruction::SSTORE;
- solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3");
- m_context.appendJumpTo(resizeEnd);
-
- m_context.adjustStackOffset(1); // we have to do that because of the jumps
-
- m_context << currentIsLong;
- m_context << Instruction::DUP3 << u256(31) << Instruction::LT;
- m_context.appendConditionalJumpTo(regularPath);
-
- // Here: long -> short
- // Read the first word of the data and store it on the stack. Clear the data location and
- // then jump to the short -> short case.
-
- // stack: ref new_length current_length ref_value
- solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
- m_context << Instruction::POP << Instruction::DUP3;
- CompilerUtils(m_context).computeHashStatic();
- m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1;
- // stack: ref new_length current_length first_word data_location
- m_context << Instruction::DUP3;
- convertLengthToSize(_type);
- m_context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1;
- // stack: ref new_length current_length first_word data_location_end data_location
- clearStorageLoop(IntegerType(256));
- m_context << Instruction::POP;
- // stack: ref new_length current_length first_word
- solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
- m_context.appendJumpTo(shortToShort);
-
- m_context << regularPath;
- // stack: ref new_length current_length ref_value
- m_context << Instruction::POP;
- }
+ TypePointer type = _typeIn.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$resizeDynamicArray_" + _typeIn.identifier(),
+ 2,
+ 0,
+ [type](CompilerContext& _context)
+ {
+ ArrayType const& _type = dynamic_cast<ArrayType const&>(*type);
+ 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.");
+
+ unsigned stackHeightStart = _context.stackHeight();
+ eth::AssemblyItem resizeEnd = _context.newTag();
+
+ // stack: ref new_length
+ // fetch old length
+ ArrayUtils(_context).retrieveLength(_type, 1);
+ // stack: ref new_length old_length
+ solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "2");
+
+ // Special case for short byte arrays, they are stored together with their length
+ if (_type.isByteArray())
+ {
+ eth::AssemblyItem regularPath = _context.newTag();
+ // We start by a large case-distinction about the old and new length of the byte array.
+
+ _context << Instruction::DUP3 << Instruction::SLOAD;
+ // stack: ref new_length current_length ref_value
+
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ _context << Instruction::DUP2 << u256(31) << Instruction::LT;
+ eth::AssemblyItem currentIsLong = _context.appendConditionalJump();
+ _context << Instruction::DUP3 << u256(31) << Instruction::LT;
+ eth::AssemblyItem newIsLong = _context.appendConditionalJump();
+
+ // Here: short -> short
+
+ // Compute 1 << (256 - 8 * new_size)
+ eth::AssemblyItem shortToShort = _context.newTag();
+ _context << shortToShort;
+ _context << Instruction::DUP3 << u256(8) << Instruction::MUL;
+ _context << u256(0x100) << Instruction::SUB;
+ _context << u256(2) << Instruction::EXP;
+ // Divide and multiply by that value, clearing bits.
+ _context << Instruction::DUP1 << Instruction::SWAP2;
+ _context << Instruction::DIV << Instruction::MUL;
+ // Insert 2*length.
+ _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
+ _context << Instruction::OR;
+ // Store.
+ _context << Instruction::DUP4 << Instruction::SSTORE;
+ solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3");
+ _context.appendJumpTo(resizeEnd);
+
+ _context.adjustStackOffset(1); // we have to do that because of the jumps
+ // Here: short -> long
+
+ _context << newIsLong;
+ // stack: ref new_length current_length ref_value
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ // Zero out lower-order byte.
+ _context << u256(0xff) << Instruction::NOT << Instruction::AND;
+ // Store at data location.
+ _context << Instruction::DUP4;
+ CompilerUtils(_context).computeHashStatic();
+ _context << Instruction::SSTORE;
+ // stack: ref new_length current_length
+ // Store new length: Compule 2*length + 1 and store it.
+ _context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD;
+ _context << u256(1) << Instruction::ADD;
+ // stack: ref new_length current_length 2*new_length+1
+ _context << Instruction::DUP4 << Instruction::SSTORE;
+ solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3");
+ _context.appendJumpTo(resizeEnd);
+
+ _context.adjustStackOffset(1); // we have to do that because of the jumps
+
+ _context << currentIsLong;
+ _context << Instruction::DUP3 << u256(31) << Instruction::LT;
+ _context.appendConditionalJumpTo(regularPath);
+
+ // Here: long -> short
+ // Read the first word of the data and store it on the stack. Clear the data location and
+ // then jump to the short -> short case.
+
+ // stack: ref new_length current_length ref_value
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ _context << Instruction::POP << Instruction::DUP3;
+ CompilerUtils(_context).computeHashStatic();
+ _context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1;
+ // stack: ref new_length current_length first_word data_location
+ _context << Instruction::DUP3;
+ ArrayUtils(_context).convertLengthToSize(_type);
+ _context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1;
+ // stack: ref new_length current_length first_word data_location_end data_location
+ ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
+ _context << Instruction::POP;
+ // stack: ref new_length current_length first_word
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ _context.appendJumpTo(shortToShort);
+
+ _context << regularPath;
+ // stack: ref new_length current_length ref_value
+ _context << Instruction::POP;
+ }
- // Change of length for a regular array (i.e. length at location, data at sha3(location)).
- // stack: ref new_length old_length
- // store new length
- m_context << Instruction::DUP2;
- if (_type.isByteArray())
- // For a "long" byte array, store length as 2*length+1
- m_context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD;
- m_context<< Instruction::DUP4 << Instruction::SSTORE;
- // skip if size is not reduced
- m_context << Instruction::DUP2 << Instruction::DUP2
- << Instruction::ISZERO << Instruction::GT;
- m_context.appendConditionalJumpTo(resizeEnd);
-
- // size reduced, clear the end of the array
- // stack: ref new_length old_length
- convertLengthToSize(_type);
- m_context << Instruction::DUP2;
- convertLengthToSize(_type);
- // stack: ref new_length old_size new_size
- // compute data positions
- m_context << Instruction::DUP4;
- CompilerUtils(m_context).computeHashStatic();
- // stack: ref new_length old_size new_size data_pos
- m_context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD;
- // stack: ref new_length data_pos new_size delete_end
- m_context << Instruction::SWAP2 << Instruction::ADD;
- // stack: ref new_length delete_end delete_start
- if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
- clearStorageLoop(IntegerType(256));
- else
- clearStorageLoop(*_type.baseType());
+ // Change of length for a regular array (i.e. length at location, data at sha3(location)).
+ // stack: ref new_length old_length
+ // store new length
+ _context << Instruction::DUP2;
+ 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;
+ // skip if size is not reduced
+ _context << Instruction::DUP2 << Instruction::DUP2
+ << Instruction::ISZERO << Instruction::GT;
+ _context.appendConditionalJumpTo(resizeEnd);
+
+ // size reduced, clear the end of the array
+ // stack: ref new_length old_length
+ ArrayUtils(_context).convertLengthToSize(_type);
+ _context << Instruction::DUP2;
+ ArrayUtils(_context).convertLengthToSize(_type);
+ // stack: ref new_length old_size new_size
+ // compute data positions
+ _context << Instruction::DUP4;
+ CompilerUtils(_context).computeHashStatic();
+ // stack: ref new_length old_size new_size data_pos
+ _context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD;
+ // stack: ref new_length data_pos new_size delete_end
+ _context << Instruction::SWAP2 << Instruction::ADD;
+ // stack: ref new_length delete_end delete_start
+ if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
+ ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
+ else
+ ArrayUtils(_context).clearStorageLoop(_type.baseType());
- m_context << resizeEnd;
- // cleanup
- m_context << Instruction::POP << Instruction::POP << Instruction::POP;
- solAssert(m_context.stackHeight() == stackHeightStart - 2, "");
+ _context << resizeEnd;
+ // cleanup
+ _context << Instruction::POP << Instruction::POP << Instruction::POP;
+ solAssert(_context.stackHeight() == stackHeightStart - 2, "");
+ }
+ );
}
-void ArrayUtils::clearStorageLoop(Type const& _type) const
+void ArrayUtils::clearStorageLoop(TypePointer const& _type) const
{
- unsigned stackHeightStart = m_context.stackHeight();
- if (_type.category() == Type::Category::Mapping)
- {
- m_context << Instruction::POP;
- return;
- }
- // stack: end_pos pos
-
- // jump to and return from the loop to allow for duplicate code removal
- eth::AssemblyItem returnTag = m_context.pushNewTag();
- m_context << Instruction::SWAP2 << Instruction::SWAP1;
-
- // stack: <return tag> end_pos pos
- eth::AssemblyItem loopStart = m_context.appendJumpToNew();
- m_context << loopStart;
- // check for loop condition
- m_context << Instruction::DUP1 << Instruction::DUP3
- << Instruction::GT << Instruction::ISZERO;
- eth::AssemblyItem zeroLoopEnd = m_context.newTag();
- m_context.appendConditionalJumpTo(zeroLoopEnd);
- // delete
- m_context << u256(0);
- StorageItem(m_context, _type).setToZero(SourceLocation(), false);
- m_context << Instruction::POP;
- // increment
- m_context << _type.storageSize() << Instruction::ADD;
- m_context.appendJumpTo(loopStart);
- // cleanup
- m_context << zeroLoopEnd;
- m_context << Instruction::POP << Instruction::SWAP1;
- // "return"
- m_context << Instruction::JUMP;
-
- m_context << returnTag;
- solAssert(m_context.stackHeight() == stackHeightStart - 1, "");
+ m_context.callLowLevelFunction(
+ "$clearStorageLoop_" + _type->identifier(),
+ 2,
+ 1,
+ [_type](CompilerContext& _context)
+ {
+ unsigned stackHeightStart = _context.stackHeight();
+ if (_type->category() == Type::Category::Mapping)
+ {
+ _context << Instruction::POP;
+ return;
+ }
+ // stack: end_pos pos
+
+ // jump to and return from the loop to allow for duplicate code removal
+ eth::AssemblyItem returnTag = _context.pushNewTag();
+ _context << Instruction::SWAP2 << Instruction::SWAP1;
+
+ // stack: <return tag> end_pos pos
+ eth::AssemblyItem loopStart = _context.appendJumpToNew();
+ _context << loopStart;
+ // check for loop condition
+ _context << Instruction::DUP1 << Instruction::DUP3
+ << Instruction::GT << Instruction::ISZERO;
+ eth::AssemblyItem zeroLoopEnd = _context.newTag();
+ _context.appendConditionalJumpTo(zeroLoopEnd);
+ // delete
+ _context << u256(0);
+ StorageItem(_context, *_type).setToZero(SourceLocation(), false);
+ _context << Instruction::POP;
+ // increment
+ _context << _type->storageSize() << Instruction::ADD;
+ _context.appendJumpTo(loopStart);
+ // cleanup
+ _context << zeroLoopEnd;
+ _context << Instruction::POP << Instruction::SWAP1;
+ // "return"
+ _context << Instruction::JUMP;
+
+ _context << returnTag;
+ solAssert(_context.stackHeight() == stackHeightStart - 1, "");
+ }
+ );
}
void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const
@@ -859,7 +901,7 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c
// check out-of-bounds access
m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO;
// out-of-bounds access throws exception
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
}
if (location == DataLocation::CallData && _arrayType.isDynamicallySized())
// remove length if present
diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h
index d0ba2892..806fbea7 100644
--- a/libsolidity/codegen/ArrayUtils.h
+++ b/libsolidity/codegen/ArrayUtils.h
@@ -22,6 +22,8 @@
#pragma once
+#include <memory>
+
namespace dev
{
namespace solidity
@@ -30,6 +32,7 @@ namespace solidity
class CompilerContext;
class Type;
class ArrayType;
+using TypePointer = std::shared_ptr<Type const>;
/**
* Class that provides code generation for handling arrays.
@@ -67,7 +70,7 @@ public:
/// 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
- void clearStorageLoop(Type const& _type) const;
+ void clearStorageLoop(TypePointer const& _type) const;
/// Converts length to size (number of storage slots or calldata/memory bytes).
/// if @a _pad then add padding to multiples of 32 bytes for calldata/memory.
/// Stack pre: length
diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp
index c14ab845..a8316109 100644
--- a/libsolidity/codegen/CompilerContext.cpp
+++ b/libsolidity/codegen/CompilerContext.cpp
@@ -21,15 +21,18 @@
*/
#include <libsolidity/codegen/CompilerContext.h>
-#include <utility>
-#include <numeric>
-#include <boost/algorithm/string/replace.hpp>
+#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/codegen/Compiler.h>
#include <libsolidity/interface/Version.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/inlineasm/AsmStack.h>
+#include <boost/algorithm/string/replace.hpp>
+
+#include <utility>
+#include <numeric>
+
using namespace std;
namespace dev
@@ -57,6 +60,62 @@ void CompilerContext::startFunction(Declaration const& _function)
*this << functionEntryLabel(_function);
}
+void CompilerContext::callLowLevelFunction(
+ string const& _name,
+ unsigned _inArgs,
+ unsigned _outArgs,
+ function<void(CompilerContext&)> const& _generator
+)
+{
+ eth::AssemblyItem retTag = pushNewTag();
+ CompilerUtils(*this).moveIntoStack(_inArgs);
+
+ *this << lowLevelFunctionTag(_name, _inArgs, _outArgs, _generator);
+
+ appendJump(eth::AssemblyItem::JumpType::IntoFunction);
+ adjustStackOffset(int(_outArgs) - 1 - _inArgs);
+ *this << retTag.tag();
+}
+
+eth::AssemblyItem CompilerContext::lowLevelFunctionTag(
+ string const& _name,
+ unsigned _inArgs,
+ unsigned _outArgs,
+ function<void(CompilerContext&)> const& _generator
+)
+{
+ auto it = m_lowLevelFunctions.find(_name);
+ if (it == m_lowLevelFunctions.end())
+ {
+ eth::AssemblyItem tag = newTag().pushTag();
+ m_lowLevelFunctions.insert(make_pair(_name, tag));
+ m_lowLevelFunctionGenerationQueue.push(make_tuple(_name, _inArgs, _outArgs, _generator));
+ return tag;
+ }
+ else
+ return it->second;
+}
+
+void CompilerContext::appendMissingLowLevelFunctions()
+{
+ while (!m_lowLevelFunctionGenerationQueue.empty())
+ {
+ string name;
+ unsigned inArgs;
+ unsigned outArgs;
+ function<void(CompilerContext&)> generator;
+ tie(name, inArgs, outArgs, generator) = m_lowLevelFunctionGenerationQueue.front();
+ m_lowLevelFunctionGenerationQueue.pop();
+
+ setStackOffset(inArgs + 1);
+ *this << m_lowLevelFunctions.at(name).tag();
+ generator(*this);
+ CompilerUtils(*this).moveToStackTop(outArgs);
+ appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
+ solAssert(stackHeight() == outArgs, "Invalid stack height in low-level function " + name + ".");
+ }
+}
+
void CompilerContext::addVariable(VariableDeclaration const& _declaration,
unsigned _offsetToCurrent)
{
@@ -167,6 +226,20 @@ CompilerContext& CompilerContext::appendJump(eth::AssemblyItem::JumpType _jumpTy
return *this << item;
}
+CompilerContext& CompilerContext::appendInvalid()
+{
+ return *this << Instruction::INVALID;
+}
+
+CompilerContext& CompilerContext::appendConditionalInvalid()
+{
+ *this << Instruction::ISZERO;
+ eth::AssemblyItem afterTag = appendConditionalJump();
+ *this << Instruction::INVALID;
+ *this << afterTag;
+ return *this;
+}
+
void CompilerContext::resetVisitedNodes(ASTNode const* _node)
{
stack<ASTNode const*> newStack;
diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h
index 80671528..c37142c9 100644
--- a/libsolidity/codegen/CompilerContext.h
+++ b/libsolidity/codegen/CompilerContext.h
@@ -22,17 +22,21 @@
#pragma once
-#include <ostream>
-#include <stack>
-#include <queue>
-#include <utility>
-#include <libevmasm/Instruction.h>
-#include <libevmasm/Assembly.h>
#include <libsolidity/ast/ASTForward.h>
#include <libsolidity/ast/Types.h>
#include <libsolidity/ast/ASTAnnotations.h>
+
+#include <libevmasm/Instruction.h>
+#include <libevmasm/Assembly.h>
+
#include <libdevcore/Common.h>
+#include <ostream>
+#include <stack>
+#include <queue>
+#include <utility>
+#include <functional>
+
namespace dev {
namespace solidity {
@@ -90,6 +94,29 @@ public:
/// as "having code".
void startFunction(Declaration const& _function);
+ /// Appends a call to the named low-level function and inserts the generator into the
+ /// list of low-level-functions to be generated, unless it already exists.
+ /// Note that the generator should not assume that objects are still alive when it is called,
+ /// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example).
+ void callLowLevelFunction(
+ std::string const& _name,
+ unsigned _inArgs,
+ unsigned _outArgs,
+ std::function<void(CompilerContext&)> const& _generator
+ );
+ /// Returns the tag of the named low-level function and inserts the generator into the
+ /// list of low-level-functions to be generated, unless it already exists.
+ /// Note that the generator should not assume that objects are still alive when it is called,
+ /// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example).
+ eth::AssemblyItem lowLevelFunctionTag(
+ std::string const& _name,
+ unsigned _inArgs,
+ unsigned _outArgs,
+ std::function<void(CompilerContext&)> const& _generator
+ );
+ /// Generates the code for missing low-level functions, i.e. calls the generators passed above.
+ void appendMissingLowLevelFunctions();
+
ModifierDefinition const& functionModifier(std::string const& _name) 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;
@@ -110,6 +137,10 @@ public:
eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); }
/// Appends a JUMP to a tag already on the stack
CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary);
+ /// Appends an INVALID instruction
+ CompilerContext& appendInvalid();
+ /// Appends a conditional INVALID instruction
+ CompilerContext& appendConditionalInvalid();
/// Returns an "ErrorTag"
eth::AssemblyItem errorTag() { return m_asm->errorTag(); }
/// Appends a JUMP to a specific tag
@@ -248,6 +279,10 @@ private:
CompilerContext *m_runtimeContext;
/// The index of the runtime subroutine.
size_t m_runtimeSub = -1;
+ /// An index of low-level function labels by name.
+ std::map<std::string, eth::AssemblyItem> m_lowLevelFunctions;
+ /// The queue of low-level functions to generate.
+ std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
};
}
diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp
index 7d382aba..477f021a 100644
--- a/libsolidity/codegen/CompilerUtils.cpp
+++ b/libsolidity/codegen/CompilerUtils.cpp
@@ -468,7 +468,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_typeOnStack);
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
enumOverflowCheckPending = false;
}
break;
@@ -497,7 +497,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_targetType);
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
enumOverflowCheckPending = false;
}
else if (targetTypeCategory == Type::Category::FixedPoint)
@@ -807,7 +807,9 @@ void CompilerUtils::pushZeroValue(Type const& _type)
{
if (funType->location() == FunctionType::Location::Internal)
{
- m_context << m_context.errorTag();
+ m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) {
+ _context.appendInvalid();
+ });
return;
}
}
@@ -820,37 +822,46 @@ void CompilerUtils::pushZeroValue(Type const& _type)
}
solAssert(referenceType->location() == DataLocation::Memory, "");
- m_context << u256(max(32u, _type.calldataEncodedSize()));
- allocateMemory();
- m_context << Instruction::DUP1;
+ TypePointer type = _type.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$pushZeroValue_" + referenceType->identifier(),
+ 0,
+ 1,
+ [type](CompilerContext& _context) {
+ CompilerUtils utils(_context);
+ _context << u256(max(32u, type->calldataEncodedSize()));
+ utils.allocateMemory();
+ _context << Instruction::DUP1;
- if (auto structType = dynamic_cast<StructType const*>(&_type))
- for (auto const& member: structType->members(nullptr))
- {
- pushZeroValue(*member.type);
- storeInMemoryDynamic(*member.type);
- }
- else if (auto arrayType = dynamic_cast<ArrayType const*>(&_type))
- {
- if (arrayType->isDynamicallySized())
- {
- // zero length
- m_context << u256(0);
- storeInMemoryDynamic(IntegerType(256));
- }
- else if (arrayType->length() > 0)
- {
- m_context << arrayType->length() << Instruction::SWAP1;
- // stack: items_to_do memory_pos
- zeroInitialiseMemoryArray(*arrayType);
- // stack: updated_memory_pos
- }
- }
- else
- solAssert(false, "Requested initialisation for unknown type: " + _type.toString());
+ if (auto structType = dynamic_cast<StructType const*>(type.get()))
+ for (auto const& member: structType->members(nullptr))
+ {
+ utils.pushZeroValue(*member.type);
+ utils.storeInMemoryDynamic(*member.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)
+ {
+ _context << arrayType->length() << Instruction::SWAP1;
+ // stack: items_to_do memory_pos
+ utils.zeroInitialiseMemoryArray(*arrayType);
+ // stack: updated_memory_pos
+ }
+ }
+ else
+ solAssert(false, "Requested initialisation for unknown type: " + type->toString());
- // remove the updated memory pointer
- m_context << Instruction::POP;
+ // remove the updated memory pointer
+ _context << Instruction::POP;
+ }
+ );
}
void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp
index a0f196bc..4d33927d 100644
--- a/libsolidity/codegen/ContractCompiler.cpp
+++ b/libsolidity/codegen/ContractCompiler.cpp
@@ -106,7 +106,7 @@ void ContractCompiler::appendCallValueCheck()
{
// Throw if function is not payable but call contained ether.
m_context << Instruction::CALLVALUE;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
}
void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract)
@@ -271,7 +271,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
appendReturnValuePacker(FunctionType(*fallback).returnParameterTypes(), _contract.isLibrary());
}
else
- m_context.appendJumpTo(m_context.errorTag());
+ m_context.appendInvalid();
for (auto const& it: interfaceFunctions)
{
@@ -486,7 +486,12 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
stackLayout.push_back(i);
stackLayout += vector<int>(c_localVariablesSize, -1);
- solAssert(stackLayout.size() <= 17, "Stack too deep, try removing local variables.");
+ if (stackLayout.size() > 17)
+ BOOST_THROW_EXCEPTION(
+ CompilerError() <<
+ errinfo_sourceLocation(_function.location()) <<
+ errinfo_comment("Stack too deep, try removing local variables.")
+ );
while (stackLayout.back() != int(stackLayout.size() - 1))
if (stackLayout.back() < 0)
{
@@ -551,6 +556,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
+ errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
for (unsigned i = 0; i < variable->type()->sizeOnStack(); ++i)
@@ -575,7 +581,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
else if (auto contract = dynamic_cast<ContractDefinition const*>(decl))
{
solAssert(contract->isLibrary(), "");
- _assembly.appendLibraryAddress(contract->name());
+ _assembly.appendLibraryAddress(contract->fullyQualifiedName());
}
else
solAssert(false, "Invalid declaration type.");
@@ -591,6 +597,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
if (stackDiff > 16 || stackDiff < 1)
BOOST_THROW_EXCEPTION(
CompilerError() <<
+ errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
for (unsigned i = 0; i < size; ++i) {
@@ -820,6 +827,7 @@ void ContractCompiler::appendMissingFunctions()
function->accept(*this);
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
}
+ m_context.appendMissingLowLevelFunctions();
}
void ContractCompiler::appendModifierOrFunctionCode()
@@ -910,7 +918,9 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime()
a << Instruction::DELEGATECALL;
//Propagate error condition (if DELEGATECALL pushes 0 on stack).
a << Instruction::ISZERO;
- a.appendJumpI(a.errorTag());
+ a << Instruction::ISZERO;
+ eth::AssemblyItem afterTag = a.appendJumpI().tag();
+ a << Instruction::INVALID << afterTag;
//@todo adjust for larger return values, make this dynamic.
a << u256(0x20) << u256(0) << Instruction::RETURN;
return make_shared<eth::Assembly>(a);
diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp
index 3922da88..b66a3e12 100644
--- a/libsolidity/codegen/ExpressionCompiler.cpp
+++ b/libsolidity/codegen/ExpressionCompiler.cpp
@@ -250,7 +250,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
}
if (lvalueSize > 0)
{
- solAssert(itemSize + lvalueSize <= 16, "Stack too deep, try removing local variables.");
+ if (itemSize + lvalueSize > 16)
+ BOOST_THROW_EXCEPTION(
+ CompilerError() <<
+ errinfo_sourceLocation(_assignment.location()) <<
+ errinfo_comment("Stack too deep, try removing local variables.")
+ );
// value [lvalue_ref] updated_value
for (unsigned i = 0; i < itemSize; ++i)
m_context << swapInstruction(itemSize + lvalueSize) << Instruction::POP;
@@ -551,20 +556,24 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
arg->accept(*this);
argumentTypes.push_back(arg->annotation().type);
}
- ContractDefinition const& contract =
- dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition();
- // copy the contract's code into memory
- eth::Assembly const& assembly = m_context.compiledContract(contract);
- utils().fetchFreeMemoryPointer();
- // TODO we create a copy here, which is actually what we want.
- // This should be revisited at the point where we fix
- // https://github.com/ethereum/solidity/issues/1092
- // pushes size
- auto subroutine = m_context.addSubroutine(make_shared<eth::Assembly>(assembly));
- m_context << Instruction::DUP1 << subroutine;
- m_context << Instruction::DUP4 << Instruction::CODECOPY;
-
- m_context << Instruction::ADD;
+ ContractDefinition const* contract =
+ &dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition();
+ m_context.callLowLevelFunction(
+ "$copyContractCreationCodeToMemory_" + contract->type()->identifier(),
+ 0,
+ 1,
+ [contract](CompilerContext& _context)
+ {
+ // copy the contract's code into memory
+ eth::Assembly const& assembly = _context.compiledContract(*contract);
+ CompilerUtils(_context).fetchFreeMemoryPointer();
+ // pushes size
+ auto subroutine = _context.addSubroutine(make_shared<eth::Assembly>(assembly));
+ _context << Instruction::DUP1 << subroutine;
+ _context << Instruction::DUP4 << Instruction::CODECOPY;
+ _context << Instruction::ADD;
+ }
+ );
utils().encodeToMemory(argumentTypes, function.parameterTypes());
// now on stack: memory_end_ptr
// need: size, offset, endowment
@@ -576,7 +585,7 @@ 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.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
if (function.valueSet())
m_context << swapInstruction(1) << Instruction::POP;
break;
@@ -892,7 +901,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
solAssert(funType->location() == FunctionType::Location::DelegateCall, "");
auto contract = dynamic_cast<ContractDefinition const*>(funType->declaration().scope());
solAssert(contract && contract->isLibrary(), "");
- m_context.appendLibraryAddress(contract->name());
+ m_context.appendLibraryAddress(contract->fullyQualifiedName());
m_context << funType->externalIdentifier();
utils().moveIntoStack(funType->selfType()->sizeOnStack(), 2);
}
@@ -1225,7 +1234,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
m_context << u256(fixedBytesType.numBytes());
m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO;
// out-of-bounds access throws exception
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
m_context << Instruction::BYTE;
m_context << (u256(1) << (256 - 8)) << Instruction::MUL;
@@ -1270,7 +1279,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
{
if (contract->isLibrary())
- m_context.appendLibraryAddress(contract->name());
+ m_context.appendLibraryAddress(contract->fullyQualifiedName());
}
else if (dynamic_cast<EventDefinition const*>(declaration))
{
@@ -1299,6 +1308,7 @@ void ExpressionCompiler::endVisit(Literal const& _literal)
{
case Type::Category::RationalNumber:
case Type::Category::Bool:
+ case Type::Category::Integer:
m_context << type->literalValue(&_literal);
break;
case Type::Category::StringLiteral:
@@ -1406,7 +1416,7 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Ty
{
// Test for division by zero
m_context << Instruction::DUP2 << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
if (_operator == Token::Div)
m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV);
@@ -1467,7 +1477,7 @@ void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type co
if (c_amountSigned)
{
m_context << u256(0) << Instruction::DUP3 << Instruction::SLT;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
}
switch (_operator)
@@ -1653,7 +1663,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
if (funKind == FunctionKind::External || funKind == FunctionKind::CallCode || funKind == FunctionKind::DelegateCall)
{
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
existenceChecked = true;
}
@@ -1689,7 +1699,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
{
//Propagate error condition (if CALL pushes 0 on stack).
m_context << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
}
utils().popStackSlots(remainsSize);
diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp
index ef3da255..fcc92dbb 100644
--- a/libsolidity/inlineasm/AsmParser.cpp
+++ b/libsolidity/inlineasm/AsmParser.cpp
@@ -71,6 +71,8 @@ assembly::Statement Parser::parseStatement()
expectToken(Token::Colon);
assignment.variableName.location = location();
assignment.variableName.name = m_scanner->currentLiteral();
+ if (instructions().count(assignment.variableName.name))
+ fatalParserError("Identifier expected, got instruction name.");
assignment.location.end = endPosition();
expectToken(Token::Identifier);
return assignment;
@@ -101,6 +103,8 @@ assembly::Statement Parser::parseStatement()
{
// functional assignment
FunctionalAssignment funAss = createWithLocation<FunctionalAssignment>(identifier.location);
+ if (instructions().count(identifier.name))
+ fatalParserError("Cannot use instruction names for identifier names.");
m_scanner->next();
funAss.variableName = identifier;
funAss.value.reset(new Statement(parseExpression()));
@@ -130,7 +134,7 @@ assembly::Statement Parser::parseExpression()
return operation;
}
-assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
+std::map<string, dev::solidity::Instruction> const& Parser::instructions()
{
// Allowed instructions, lowercase names.
static map<string, dev::solidity::Instruction> s_instructions;
@@ -151,7 +155,11 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
// add alias for selfdestruct
s_instructions["selfdestruct"] = solidity::Instruction::SUICIDE;
}
+ return s_instructions;
+}
+assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
+{
Statement ret;
switch (m_scanner->currentToken())
{
@@ -170,9 +178,9 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
else
literal = m_scanner->currentLiteral();
// first search the set of instructions.
- if (s_instructions.count(literal))
+ if (instructions().count(literal))
{
- dev::solidity::Instruction const& instr = s_instructions[literal];
+ dev::solidity::Instruction const& instr = instructions().at(literal);
if (_onlySinglePusher)
{
InstructionInfo info = dev::solidity::instructionInfo(instr);
@@ -207,6 +215,8 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration()
VariableDeclaration varDecl = createWithLocation<VariableDeclaration>();
expectToken(Token::Let);
varDecl.name = m_scanner->currentLiteral();
+ if (instructions().count(varDecl.name))
+ fatalParserError("Cannot use instruction names for identifier names.");
expectToken(Token::Identifier);
expectToken(Token::Colon);
expectToken(Token::Assign);
diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h
index 8b56ab90..643548dd 100644
--- a/libsolidity/inlineasm/AsmParser.h
+++ b/libsolidity/inlineasm/AsmParser.h
@@ -64,6 +64,7 @@ protected:
Statement parseStatement();
/// Parses a functional expression that has to push exactly one stack element
Statement parseExpression();
+ std::map<std::string, dev::solidity::Instruction> const& instructions();
Statement parseElementaryOperation(bool _onlySinglePusher = false);
VariableDeclaration parseVariableDeclaration();
FunctionalInstruction parseFunctionalInstruction(Statement&& _instruction);
diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp
index a31df584..3335c40e 100644
--- a/libsolidity/interface/CompilerStack.cpp
+++ b/libsolidity/interface/CompilerStack.cpp
@@ -21,6 +21,7 @@
* Full-stack compiler that converts a source code string to bytecode.
*/
+
#include <libsolidity/interface/CompilerStack.h>
#include <libsolidity/interface/Version.h>
@@ -111,6 +112,7 @@ bool CompilerStack::parse()
{
//reset
m_errors.clear();
+ ASTNode::resetID();
m_parseSuccessful = false;
if (SemVerVersion{string(VersionString)}.isPrerelease())
@@ -180,11 +182,15 @@ bool CompilerStack::parse()
if (!resolver.updateDeclaration(*m_globalContext->currentThis())) return false;
if (!resolver.updateDeclaration(*m_globalContext->currentSuper())) return false;
if (!resolver.resolveNamesAndTypes(*contract)) return false;
- m_contracts[contract->name()].contract = contract;
- }
- if (!checkLibraryNameClashes())
- noErrors = 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;
+ }
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
@@ -201,7 +207,13 @@ bool CompilerStack::parse()
else
noErrors = false;
- m_contracts[contract->name()].contract = contract;
+ // 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)
@@ -315,6 +327,27 @@ string const* CompilerStack::runtimeSourceMapping(string const& _contractName) c
return c.runtimeSourceMapping.get();
}
+std::string const CompilerStack::filesystemFriendlyName(string const& _contractName) const
+{
+ // Look up the contract (by its fully-qualified name)
+ Contract const& matchContract = m_contracts.at(_contractName);
+ // Check to see if it could collide on name
+ for (auto const& contract: m_contracts)
+ {
+ if (contract.second.contract->name() == matchContract.contract->name() &&
+ contract.second.contract != matchContract.contract)
+ {
+ // If it does, then return its fully-qualified name, made fs-friendly
+ std::string friendlyName = boost::algorithm::replace_all_copy(_contractName, "/", "_");
+ boost::algorithm::replace_all(friendlyName, ":", "_");
+ boost::algorithm::replace_all(friendlyName, ".", "_");
+ return friendlyName;
+ }
+ }
+ // If no collision, return the contract's name
+ return matchContract.contract->name();
+}
+
eth::LinkerObject const& CompilerStack::object(string const& _contractName) const
{
return contract(_contractName).object;
@@ -569,37 +602,6 @@ void CompilerStack::resolveImports()
swap(m_sourceOrder, sourceOrder);
}
-bool CompilerStack::checkLibraryNameClashes()
-{
- bool clashFound = false;
- map<string, SourceLocation> libraries;
- for (Source const* source: m_sourceOrder)
- for (ASTPointer<ASTNode> const& node: source->ast->nodes())
- if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
- if (contract->isLibrary())
- {
- if (libraries.count(contract->name()))
- {
- auto err = make_shared<Error>(Error::Type::DeclarationError);
- *err <<
- errinfo_sourceLocation(contract->location()) <<
- errinfo_comment(
- "Library \"" + contract->name() + "\" declared twice "
- "(will create ambiguities during linking)."
- ) <<
- errinfo_secondarySourceLocation(SecondarySourceLocation().append(
- "The other declaration is here:", libraries[contract->name()]
- ));
-
- m_errors.push_back(err);
- clashFound = true;
- }
- else
- libraries[contract->name()] = contract->location();
- }
- return !clashFound;
-}
-
string CompilerStack::absolutePath(string const& _path, string const& _reference) const
{
using path = boost::filesystem::path;
@@ -622,13 +624,17 @@ void CompilerStack::compileContract(
map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts
)
{
- if (_compiledContracts.count(&_contract) || !_contract.annotation().isFullyImplemented)
+ if (
+ _compiledContracts.count(&_contract) ||
+ !_contract.annotation().isFullyImplemented ||
+ !_contract.annotation().hasPublicConstructor
+ )
return;
for (auto const* dependency: _contract.annotation().contractDependencies)
compileContract(*dependency, _compiledContracts);
shared_ptr<Compiler> compiler = make_shared<Compiler>(m_optimize, m_optimizeRuns);
- Contract& compiledContract = m_contracts.at(_contract.name());
+ Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
string onChainMetadata = createOnChainMetadata(compiledContract);
bytes cborEncodedMetadata =
// CBOR-encoding of {"bzzr0": dev::swarmHash(onChainMetadata)}
@@ -674,10 +680,27 @@ CompilerStack::Contract const& CompilerStack::contract(string const& _contractNa
for (auto const& it: m_sources)
for (ASTPointer<ASTNode> const& node: it.second.ast->nodes())
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
- contractName = contract->name();
+ contractName = contract->fullyQualifiedName();
auto it = m_contracts.find(contractName);
- if (it == m_contracts.end())
+ // To provide a measure of backward-compatibility, if a contract is not located by its
+ // fully-qualified name, a lookup will be attempted purely on the contract's name to see
+ // if anything will satisfy.
+ if (it == m_contracts.end() && contractName.find(":") == string::npos)
+ {
+ for (auto const& contractEntry: m_contracts)
+ {
+ stringstream ss;
+ ss.str(contractEntry.first);
+ // All entries are <source>:<contract>
+ string source;
+ string foundName;
+ getline(ss, source, ':');
+ getline(ss, foundName, ':');
+ if (foundName == contractName) return contractEntry.second;
+ }
+ // If we get here, both lookup methods failed.
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract " + _contractName + " not found."));
+ }
return it->second;
}
@@ -695,7 +718,7 @@ string CompilerStack::createOnChainMetadata(Contract const& _contract) const
Json::Value meta;
meta["version"] = 1;
meta["language"] = "Solidity";
- meta["compiler"]["version"] = VersionString;
+ meta["compiler"]["version"] = VersionStringStrict;
meta["sources"] = Json::objectValue;
for (auto const& s: m_sources)
@@ -703,10 +726,15 @@ string CompilerStack::createOnChainMetadata(Contract const& _contract) const
solAssert(s.second.scanner, "Scanner not available");
meta["sources"][s.first]["keccak256"] =
"0x" + toHex(dev::keccak256(s.second.scanner->source()).asBytes());
- meta["sources"][s.first]["urls"] = Json::arrayValue;
- meta["sources"][s.first]["urls"].append(
- "bzzr://" + toHex(dev::swarmHash(s.second.scanner->source()).asBytes())
- );
+ if (m_metadataLiteralSources)
+ meta["sources"][s.first]["content"] = s.second.scanner->source();
+ else
+ {
+ meta["sources"][s.first]["urls"] = Json::arrayValue;
+ meta["sources"][s.first]["urls"].append(
+ "bzzr://" + toHex(dev::swarmHash(s.second.scanner->source()).asBytes())
+ );
+ }
}
meta["settings"]["optimizer"]["enabled"] = m_optimize;
meta["settings"]["optimizer"]["runs"] = m_optimizeRuns;
diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h
index d49a8df1..9ee70215 100644
--- a/libsolidity/interface/CompilerStack.h
+++ b/libsolidity/interface/CompilerStack.h
@@ -148,6 +148,10 @@ public:
/// @returns the string that provides a mapping between runtime bytecode and sourcecode.
/// if the contract does not (yet) have bytecode.
std::string const* runtimeSourceMapping(std::string const& _contractName = "") const;
+
+ /// @returns either the contract's name or a mixture of its name and source file, sanitized for filesystem use
+ std::string const filesystemFriendlyName(std::string const& _contractName) const;
+
/// @returns hash of the runtime bytecode for the contract, i.e. the code that is
/// returned by the constructor or the zero-h256 if the contract still needs to be linked or
/// does not have runtime code.
@@ -173,6 +177,7 @@ public:
/// Can be one of 4 types defined at @c DocumentationType
Json::Value const& metadata(std::string const& _contractName, DocumentationType _type) const;
std::string const& onChainMetadata(std::string const& _contractName) const;
+ void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; }
/// @returns the previously used scanner, useful for counting lines during error reporting.
Scanner const& scanner(std::string const& _sourceName = "") const;
@@ -230,9 +235,6 @@ private:
StringMap loadMissingSources(SourceUnit const& _ast, std::string const& _path);
std::string applyRemapping(std::string const& _path, std::string const& _context);
void resolveImports();
- /// Checks whether there are libraries with the same name, reports that as an error and
- /// @returns false in this case.
- bool checkLibraryNameClashes();
/// @returns the absolute path corresponding to @a _path relative to @a _reference.
std::string absolutePath(std::string const& _path, std::string const& _reference) const;
/// Helper function to return path converted strings.
@@ -273,6 +275,7 @@ private:
std::map<std::string const, Contract> m_contracts;
std::string m_formalTranslation;
ErrorList m_errors;
+ bool m_metadataLiteralSources = false;
};
}
diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp
index 9944bb22..6c1bb0c4 100644
--- a/libsolidity/interface/InterfaceHandler.cpp
+++ b/libsolidity/interface/InterfaceHandler.cpp
@@ -30,20 +30,6 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe
{
Json::Value abi(Json::arrayValue);
- auto populateParameters = [](vector<string> const& _paramNames, vector<string> const& _paramTypes)
- {
- Json::Value params(Json::arrayValue);
- solAssert(_paramNames.size() == _paramTypes.size(), "Names and types vector size does not match");
- for (unsigned i = 0; i < _paramNames.size(); ++i)
- {
- Json::Value param;
- param["name"] = _paramNames[i];
- param["type"] = _paramTypes[i];
- params.append(param);
- }
- return params;
- };
-
for (auto it: _contractDef.interfaceFunctions())
{
auto externalFunctionType = it.second->interfaceFunctionType();
@@ -52,13 +38,15 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe
method["name"] = it.second->declaration().name();
method["constant"] = it.second->isConstant();
method["payable"] = it.second->isPayable();
- method["inputs"] = populateParameters(
+ method["inputs"] = formatTypeList(
externalFunctionType->parameterNames(),
- externalFunctionType->parameterTypeNames(_contractDef.isLibrary())
+ externalFunctionType->parameterTypes(),
+ _contractDef.isLibrary()
);
- method["outputs"] = populateParameters(
+ method["outputs"] = formatTypeList(
externalFunctionType->returnParameterNames(),
- externalFunctionType->returnParameterTypeNames(_contractDef.isLibrary())
+ externalFunctionType->returnParameterTypes(),
+ _contractDef.isLibrary()
);
abi.append(method);
}
@@ -69,9 +57,10 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe
auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType();
solAssert(!!externalFunction, "");
method["payable"] = externalFunction->isPayable();
- method["inputs"] = populateParameters(
+ method["inputs"] = formatTypeList(
externalFunction->parameterNames(),
- externalFunction->parameterTypeNames(_contractDef.isLibrary())
+ externalFunction->parameterTypes(),
+ _contractDef.isLibrary()
);
abi.append(method);
}
@@ -179,6 +168,25 @@ Json::Value InterfaceHandler::devDocumentation(ContractDefinition const& _contra
return doc;
}
+Json::Value InterfaceHandler::formatTypeList(
+ vector<string> const& _names,
+ vector<TypePointer> const& _types,
+ bool _forLibrary
+)
+{
+ Json::Value params(Json::arrayValue);
+ solAssert(_names.size() == _types.size(), "Names and types vector size does not match");
+ for (unsigned i = 0; i < _names.size(); ++i)
+ {
+ solAssert(_types[i], "");
+ Json::Value param;
+ param["name"] = _names[i];
+ param["type"] = _types[i]->canonicalName(_forLibrary);
+ params.append(param);
+ }
+ return params;
+}
+
string InterfaceHandler::extractDoc(multimap<string, DocTag> const& _tags, string const& _name)
{
string value;
diff --git a/libsolidity/interface/InterfaceHandler.h b/libsolidity/interface/InterfaceHandler.h
index b7e1bb00..56927d44 100644
--- a/libsolidity/interface/InterfaceHandler.h
+++ b/libsolidity/interface/InterfaceHandler.h
@@ -37,6 +37,8 @@ namespace solidity
// Forward declarations
class ContractDefinition;
+class Type;
+using TypePointer = std::shared_ptr<Type const>;
struct DocTag;
enum class DocumentationType: uint8_t;
@@ -84,6 +86,14 @@ public:
static Json::Value devDocumentation(ContractDefinition const& _contractDef);
private:
+ /// @returns a json value suitable for a list of types in function input or output
+ /// parameters or other places. If @a _forLibrary is true, complex types are referenced
+ /// by name, otherwise they are anonymously expanded.
+ static Json::Value formatTypeList(
+ std::vector<std::string> const& _names,
+ std::vector<TypePointer> const& _types,
+ bool _forLibrary
+ );
/// @returns concatenation of all content under the given tag name.
static std::string extractDoc(std::multimap<std::string, DocTag> const& _tags, std::string const& _name);
};
diff --git a/libsolidity/interface/Version.cpp b/libsolidity/interface/Version.cpp
index ff66f039..0d23f9c3 100644
--- a/libsolidity/interface/Version.cpp
+++ b/libsolidity/interface/Version.cpp
@@ -38,6 +38,10 @@ string const dev::solidity::VersionString =
(string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) +
(string(SOL_VERSION_BUILDINFO).empty() ? "" : "+" + string(SOL_VERSION_BUILDINFO));
+string const dev::solidity::VersionStringStrict =
+ string(dev::solidity::VersionNumber) +
+ (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) +
+ (string(SOL_VERSION_COMMIT).empty() ? "" : "+" + string(SOL_VERSION_COMMIT));
bytes dev::solidity::binaryVersion()
{
diff --git a/libsolidity/interface/Version.h b/libsolidity/interface/Version.h
index 5b07b3f4..24c3555d 100644
--- a/libsolidity/interface/Version.h
+++ b/libsolidity/interface/Version.h
@@ -32,6 +32,7 @@ namespace solidity
extern char const* VersionNumber;
extern std::string const VersionString;
+extern std::string const VersionStringStrict;
/// @returns a binary form of the version string, where A.B.C-HASH is encoded such that
/// the first byte is zero, the following three bytes encode A B and C (interpreted as decimals)
diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp
index bbee35f5..8e912126 100644
--- a/libsolidity/parsing/DocStringParser.cpp
+++ b/libsolidity/parsing/DocStringParser.cpp
@@ -1,14 +1,19 @@
#include <libsolidity/parsing/DocStringParser.h>
-#include <boost/range/irange.hpp>
#include <libsolidity/interface/Utils.h>
+#include <boost/range/irange.hpp>
+#include <boost/range/algorithm.hpp>
+
using namespace std;
using namespace dev;
using namespace dev::solidity;
-static inline string::const_iterator skipLineOrEOS(
+namespace
+{
+
+string::const_iterator skipLineOrEOS(
string::const_iterator _nlPos,
string::const_iterator _end
)
@@ -16,14 +21,34 @@ static inline string::const_iterator skipLineOrEOS(
return (_nlPos == _end) ? _end : ++_nlPos;
}
-static inline string::const_iterator firstSpaceOrNl(
+string::const_iterator firstSpaceOrTab(
string::const_iterator _pos,
string::const_iterator _end
)
{
- auto spacePos = find(_pos, _end, ' ');
- auto nlPos = find(_pos, _end, '\n');
- return (spacePos < nlPos) ? spacePos : nlPos;
+ return boost::range::find_first_of(make_pair(_pos, _end), " \t");
+}
+
+string::const_iterator firstWhitespaceOrNewline(
+ string::const_iterator _pos,
+ string::const_iterator _end
+)
+{
+ return boost::range::find_first_of(make_pair(_pos, _end), " \t\n");
+}
+
+
+string::const_iterator skipWhitespace(
+ string::const_iterator _pos,
+ string::const_iterator _end
+)
+{
+ auto currPos = _pos;
+ while (currPos != _end && (*currPos == ' ' || *currPos == '\t'))
+ currPos += 1;
+ return currPos;
+}
+
}
bool DocStringParser::parse(string const& _docString, ErrorList& _errors)
@@ -43,7 +68,7 @@ bool DocStringParser::parse(string const& _docString, ErrorList& _errors)
if (tagPos != end && tagPos < nlPos)
{
// we found a tag
- auto tagNameEndPos = firstSpaceOrNl(tagPos, end);
+ auto tagNameEndPos = firstWhitespaceOrNewline(tagPos, end);
if (tagNameEndPos == end)
{
appendError("End of tag " + string(tagPos, tagNameEndPos) + "not found");
@@ -75,27 +100,40 @@ DocStringParser::iter DocStringParser::parseDocTagLine(iter _pos, iter _end, boo
{
solAssert(!!m_lastTag, "");
auto nlPos = find(_pos, _end, '\n');
- if (_appending && _pos < _end && *_pos != ' ')
+ if (_appending && _pos < _end && *_pos != ' ' && *_pos != '\t')
m_lastTag->content += " ";
+ else if (!_appending)
+ _pos = skipWhitespace(_pos, _end);
copy(_pos, nlPos, back_inserter(m_lastTag->content));
return skipLineOrEOS(nlPos, _end);
}
DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end)
{
- // find param name
- auto currPos = find(_pos, _end, ' ');
- if (currPos == _end)
+ // find param name start
+ auto nameStartPos = skipWhitespace(_pos, _end);
+ if (nameStartPos == _end)
{
- appendError("End of param name not found" + string(_pos, _end));
+ appendError("No param name given");
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 paramName = string(_pos, currPos);
+ auto descStartPos = skipWhitespace(nameEndPos, _end);
+ if (descStartPos == _end)
+ {
+ appendError("No description given for param " + paramName);
+ return _end;
+ }
- currPos += 1;
- auto nlPos = find(currPos, _end, '\n');
- auto paramDesc = string(currPos, nlPos);
+ auto nlPos = find(descStartPos, _end, '\n');
+ auto paramDesc = string(descStartPos, nlPos);
newTag("param");
m_lastTag->paramName = paramName;
m_lastTag->content = paramDesc;