diff options
author | Gav Wood <i@gavwood.com> | 2015-02-21 04:56:37 +0800 |
---|---|---|
committer | Gav Wood <i@gavwood.com> | 2015-02-21 04:56:37 +0800 |
commit | 89d84edb16812ff9e4b1049ee0257d65c75f5a3c (patch) | |
tree | baeda13e44a8fc0f6ac2f705fdf334da14405f39 | |
parent | d552ceb50f8e3888abf5b9a6095a5fb60ee91b5f (diff) | |
parent | 852405116654bda9f4dc1d4876a2368184b30a8c (diff) | |
download | dexon-solidity-89d84edb16812ff9e4b1049ee0257d65c75f5a3c.tar dexon-solidity-89d84edb16812ff9e4b1049ee0257d65c75f5a3c.tar.gz dexon-solidity-89d84edb16812ff9e4b1049ee0257d65c75f5a3c.tar.bz2 dexon-solidity-89d84edb16812ff9e4b1049ee0257d65c75f5a3c.tar.lz dexon-solidity-89d84edb16812ff9e4b1049ee0257d65c75f5a3c.tar.xz dexon-solidity-89d84edb16812ff9e4b1049ee0257d65c75f5a3c.tar.zst dexon-solidity-89d84edb16812ff9e4b1049ee0257d65c75f5a3c.zip |
Merge branch 'develop'
Conflicts:
README.md
evmjit
libdevcrypto/CryptoPP.cpp
libethereum/State.cpp
neth/main.cpp
42 files changed, 5342 insertions, 1687 deletions
@@ -27,6 +27,8 @@ #include <libsolidity/Exceptions.h> #include <libsolidity/AST_accept.h> +#include <libdevcrypto/SHA3.h> + using namespace std; namespace dev @@ -39,40 +41,199 @@ TypeError ASTNode::createTypeError(string const& _description) const return TypeError() << errinfo_sourceLocation(getLocation()) << errinfo_comment(_description); } +TypePointer ContractDefinition::getType(ContractDefinition const* _currentContract) const +{ + return make_shared<TypeType>(make_shared<ContractType>(*this), _currentContract); +} + void ContractDefinition::checkTypeRequirements() { + for (ASTPointer<InheritanceSpecifier> const& baseSpecifier: getBaseContracts()) + baseSpecifier->checkTypeRequirements(); + + checkIllegalOverrides(); + FunctionDefinition const* constructor = getConstructor(); if (constructor && !constructor->getReturnParameters().empty()) BOOST_THROW_EXCEPTION(constructor->getReturnParameterList()->createTypeError( - "Non-empty \"returns\" directive for constructor.")); + "Non-empty \"returns\" directive for constructor.")); + + FunctionDefinition const* fallbackFunction = nullptr; + for (ASTPointer<FunctionDefinition> const& function: getDefinedFunctions()) + if (function->getName().empty()) + { + if (fallbackFunction) + BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_comment("Only one fallback function is allowed.")); + else + { + fallbackFunction = function.get(); + if (!fallbackFunction->getParameters().empty()) + BOOST_THROW_EXCEPTION(fallbackFunction->getParameterList().createTypeError("Fallback function cannot take parameters.")); + } + } + for (ASTPointer<ModifierDefinition> const& modifier: getFunctionModifiers()) + modifier->checkTypeRequirements(); for (ASTPointer<FunctionDefinition> const& function: getDefinedFunctions()) function->checkTypeRequirements(); + + // check for hash collisions in function signatures + set<FixedHash<4>> hashes; + for (auto const& it: getInterfaceFunctionList()) + { + FixedHash<4> const& hash = it.first; + if (hashes.count(hash)) + BOOST_THROW_EXCEPTION(createTypeError( + std::string("Function signature hash collision for ") + + it.second->getCanonicalSignature())); + hashes.insert(hash); + } } -vector<FunctionDefinition const*> ContractDefinition::getInterfaceFunctions() const +map<FixedHash<4>, FunctionTypePointer> ContractDefinition::getInterfaceFunctions() const { - vector<FunctionDefinition const*> exportedFunctions; - for (ASTPointer<FunctionDefinition> const& f: m_definedFunctions) - if (f->isPublic() && f->getName() != getName()) - exportedFunctions.push_back(f.get()); - auto compareNames = [](FunctionDefinition const* _a, FunctionDefinition const* _b) - { - return _a->getName().compare(_b->getName()) < 0; - }; + auto exportedFunctionList = getInterfaceFunctionList(); + + map<FixedHash<4>, FunctionTypePointer> exportedFunctions; + for (auto const& it: exportedFunctionList) + exportedFunctions.insert(it); + + solAssert(exportedFunctionList.size() == exportedFunctions.size(), + "Hash collision at Function Definition Hash calculation"); - sort(exportedFunctions.begin(), exportedFunctions.end(), compareNames); return exportedFunctions; } FunctionDefinition const* ContractDefinition::getConstructor() const { for (ASTPointer<FunctionDefinition> const& f: m_definedFunctions) - if (f->getName() == getName()) + if (f->isConstructor()) return f.get(); return nullptr; } +FunctionDefinition const* ContractDefinition::getFallbackFunction() const +{ + for (ContractDefinition const* contract: getLinearizedBaseContracts()) + for (ASTPointer<FunctionDefinition> const& f: contract->getDefinedFunctions()) + if (f->getName().empty()) + return f.get(); + return nullptr; +} + +void ContractDefinition::checkIllegalOverrides() const +{ + // TODO unify this at a later point. for this we need to put the constness and the access specifier + // into the types + map<string, FunctionDefinition const*> functions; + map<string, ModifierDefinition const*> modifiers; + + // We search from derived to base, so the stored item causes the error. + for (ContractDefinition const* contract: getLinearizedBaseContracts()) + { + for (ASTPointer<FunctionDefinition> const& function: contract->getDefinedFunctions()) + { + if (function->isConstructor()) + continue; // constructors can neither be overridden nor override anything + string const& name = function->getName(); + if (modifiers.count(name)) + BOOST_THROW_EXCEPTION(modifiers[name]->createTypeError("Override changes function to modifier.")); + FunctionDefinition const*& override = functions[name]; + if (!override) + override = function.get(); + else if (override->getVisibility() != function->getVisibility() || + override->isDeclaredConst() != function->isDeclaredConst() || + FunctionType(*override) != FunctionType(*function)) + BOOST_THROW_EXCEPTION(override->createTypeError("Override changes extended function signature.")); + } + for (ASTPointer<ModifierDefinition> const& modifier: contract->getFunctionModifiers()) + { + string const& name = modifier->getName(); + if (functions.count(name)) + BOOST_THROW_EXCEPTION(functions[name]->createTypeError("Override changes modifier to function.")); + ModifierDefinition const*& override = modifiers[name]; + if (!override) + override = modifier.get(); + else if (ModifierType(*override) != ModifierType(*modifier)) + BOOST_THROW_EXCEPTION(override->createTypeError("Override changes modifier signature.")); + } + } +} + +std::vector<ASTPointer<EventDefinition>> const& ContractDefinition::getInterfaceEvents() const +{ + if (!m_interfaceEvents) + { + set<string> eventsSeen; + m_interfaceEvents.reset(new std::vector<ASTPointer<EventDefinition>>()); + for (ContractDefinition const* contract: getLinearizedBaseContracts()) + for (ASTPointer<EventDefinition> const& e: contract->getEvents()) + if (eventsSeen.count(e->getName()) == 0) + { + eventsSeen.insert(e->getName()); + m_interfaceEvents->push_back(e); + } + } + return *m_interfaceEvents; +} + +vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::getInterfaceFunctionList() const +{ + if (!m_interfaceFunctionList) + { + set<string> functionsSeen; + m_interfaceFunctionList.reset(new vector<pair<FixedHash<4>, FunctionTypePointer>>()); + for (ContractDefinition const* contract: getLinearizedBaseContracts()) + { + for (ASTPointer<FunctionDefinition> const& f: contract->getDefinedFunctions()) + if (f->isPublic() && !f->isConstructor() && !f->getName().empty() && functionsSeen.count(f->getName()) == 0) + { + functionsSeen.insert(f->getName()); + FixedHash<4> hash(dev::sha3(f->getCanonicalSignature())); + m_interfaceFunctionList->push_back(make_pair(hash, make_shared<FunctionType>(*f, false))); + } + + for (ASTPointer<VariableDeclaration> const& v: contract->getStateVariables()) + if (v->isPublic() && functionsSeen.count(v->getName()) == 0) + { + FunctionType ftype(*v); + functionsSeen.insert(v->getName()); + FixedHash<4> hash(dev::sha3(ftype.getCanonicalSignature(v->getName()))); + m_interfaceFunctionList->push_back(make_pair(hash, make_shared<FunctionType>(*v))); + } + } + } + return *m_interfaceFunctionList; +} + +TypePointer EnumValue::getType(ContractDefinition const*) const +{ + EnumDefinition const* parentDef = dynamic_cast<EnumDefinition const*>(getScope()); + solAssert(parentDef, "Enclosing Scope of EnumValue was not set"); + return make_shared<EnumType>(*parentDef); +} + +void InheritanceSpecifier::checkTypeRequirements() +{ + m_baseName->checkTypeRequirements(); + for (ASTPointer<Expression> const& argument: m_arguments) + argument->checkTypeRequirements(); + + ContractDefinition const* base = dynamic_cast<ContractDefinition const*>(m_baseName->getReferencedDeclaration()); + solAssert(base, "Base contract not available."); + TypePointers parameterTypes = ContractType(*base).getConstructorType()->getParameterTypes(); + if (parameterTypes.size() != m_arguments.size()) + BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for constructor call.")); + for (size_t i = 0; i < m_arguments.size(); ++i) + if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[i])) + BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in constructer call.")); +} + +TypePointer StructDefinition::getType(ContractDefinition const*) const +{ + return make_shared<TypeType>(make_shared<StructType>(*this)); +} + void StructDefinition::checkMemberTypes() const { for (ASTPointer<VariableDeclaration> const& member: getMembers()) @@ -93,7 +254,7 @@ void StructDefinition::checkRecursion() const << errinfo_comment("Recursive struct definition.")); definitionsSeen.insert(def); for (ASTPointer<VariableDeclaration> const& member: def->getMembers()) - if (member->getType()->getCategory() == Type::Category::STRUCT) + if (member->getType()->getCategory() == Type::Category::Struct) { UserDefinedTypeName const& typeName = dynamic_cast<UserDefinedTypeName const&>(*member->getTypeName()); queue.push_back(&dynamic_cast<StructDefinition const&>(*typeName.getReferencedDeclaration())); @@ -101,15 +262,89 @@ void StructDefinition::checkRecursion() const } } +TypePointer EnumDefinition::getType(ContractDefinition const*) const +{ + return make_shared<TypeType>(make_shared<EnumType>(*this)); +} + +TypePointer FunctionDefinition::getType(ContractDefinition const*) const +{ + return make_shared<FunctionType>(*this); +} + void FunctionDefinition::checkTypeRequirements() { for (ASTPointer<VariableDeclaration> const& var: getParameters() + getReturnParameters()) if (!var->getType()->canLiveOutsideStorage()) BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage.")); + for (ASTPointer<ModifierInvocation> const& modifier: m_functionModifiers) + modifier->checkTypeRequirements(); + + m_body->checkTypeRequirements(); +} + +string FunctionDefinition::getCanonicalSignature() const +{ + return FunctionType(*this).getCanonicalSignature(getName()); +} + +bool VariableDeclaration::isLValue() const +{ + // External function parameters are Read-Only + return !isExternalFunctionParameter(); +} + +bool VariableDeclaration::isExternalFunctionParameter() const +{ + auto const* function = dynamic_cast<FunctionDefinition const*>(getScope()); + if (!function || function->getVisibility() != Declaration::Visibility::External) + return false; + for (auto const& variable: function->getParameters()) + if (variable.get() == this) + return true; + return false; +} +TypePointer ModifierDefinition::getType(ContractDefinition const*) const +{ + return make_shared<ModifierType>(*this); +} + +void ModifierDefinition::checkTypeRequirements() +{ m_body->checkTypeRequirements(); } +void ModifierInvocation::checkTypeRequirements() +{ + m_modifierName->checkTypeRequirements(); + for (ASTPointer<Expression> const& argument: m_arguments) + argument->checkTypeRequirements(); + + ModifierDefinition const* modifier = dynamic_cast<ModifierDefinition const*>(m_modifierName->getReferencedDeclaration()); + solAssert(modifier, "Function modifier not found."); + vector<ASTPointer<VariableDeclaration>> const& parameters = modifier->getParameters(); + if (parameters.size() != m_arguments.size()) + BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for modifier invocation.")); + for (size_t i = 0; i < m_arguments.size(); ++i) + if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameters[i]->getType())) + BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in modifier invocation.")); +} + +void EventDefinition::checkTypeRequirements() +{ + int numIndexed = 0; + for (ASTPointer<VariableDeclaration> const& var: getParameters()) + { + if (var->isIndexed()) + numIndexed++; + if (!var->getType()->canLiveOutsideStorage()) + BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage.")); + } + if (numIndexed > 3) + BOOST_THROW_EXCEPTION(createTypeError("More than 3 indexed arguments for event.")); +} + void Block::checkTypeRequirements() { for (shared_ptr<Statement> const& statement: m_statements) @@ -145,7 +380,8 @@ void Return::checkTypeRequirements() { if (!m_expression) return; - solAssert(m_returnParameters, "Return parameters not assigned."); + if (!m_returnParameters) + BOOST_THROW_EXCEPTION(createTypeError("Return arguments not allowed.")); if (m_returnParameters->getParameters().size() != 1) BOOST_THROW_EXCEPTION(createTypeError("Different number of arguments in return statement " "than in returns declaration.")); @@ -168,7 +404,17 @@ void VariableDefinition::checkTypeRequirements() { // no type declared and no previous assignment, infer the type m_value->checkTypeRequirements(); - m_variable->setType(m_value->getType()); + TypePointer type = m_value->getType(); + if (type->getCategory() == Type::Category::IntegerConstant) + { + auto intType = dynamic_pointer_cast<IntegerConstantType const>(type)->getIntegerType(); + if (!intType) + BOOST_THROW_EXCEPTION(m_value->createTypeError("Invalid integer constant " + type->toString())); + type = intType; + } + else if (type->getCategory() == Type::Category::Void) + BOOST_THROW_EXCEPTION(m_variable->createTypeError("var cannot be void type")); + m_variable->setType(type); } } } @@ -177,20 +423,31 @@ void Assignment::checkTypeRequirements() { m_leftHandSide->checkTypeRequirements(); m_leftHandSide->requireLValue(); - //@todo later, assignments to structs might be possible, but not to mappings - if (!m_leftHandSide->getType()->isValueType() && !m_leftHandSide->isLocalLValue()) - BOOST_THROW_EXCEPTION(createTypeError("Assignment to non-local non-value lvalue.")); - m_rightHandSide->expectType(*m_leftHandSide->getType()); + if (m_leftHandSide->getType()->getCategory() == Type::Category::Mapping) + BOOST_THROW_EXCEPTION(createTypeError("Mappings cannot be assigned to.")); m_type = m_leftHandSide->getType(); - if (m_assigmentOperator != Token::ASSIGN) + if (m_assigmentOperator == Token::Assign) + m_rightHandSide->expectType(*m_type); + else + { // compound assignment - if (!m_type->acceptsBinaryOperator(Token::AssignmentToBinaryOp(m_assigmentOperator))) - BOOST_THROW_EXCEPTION(createTypeError("Operator not compatible with type.")); + m_rightHandSide->checkTypeRequirements(); + TypePointer resultType = m_type->binaryOperatorResult(Token::AssignmentToBinaryOp(m_assigmentOperator), + m_rightHandSide->getType()); + if (!resultType || *resultType != *m_type) + BOOST_THROW_EXCEPTION(createTypeError("Operator " + string(Token::toString(m_assigmentOperator)) + + " not compatible with types " + + m_type->toString() + " and " + + m_rightHandSide->getType()->toString())); + } } void ExpressionStatement::checkTypeRequirements() { m_expression->checkTypeRequirements(); + if (m_expression->getType()->getCategory() == Type::Category::IntegerConstant) + if (!dynamic_pointer_cast<IntegerConstantType const>(m_expression->getType())->getIntegerType()) + BOOST_THROW_EXCEPTION(m_expression->createTypeError("Invalid integer constant.")); } void Expression::expectType(Type const& _expectedType) @@ -212,12 +469,12 @@ void Expression::requireLValue() void UnaryOperation::checkTypeRequirements() { - // INC, DEC, ADD, SUB, NOT, BIT_NOT, DELETE + // Inc, Dec, Add, Sub, Not, BitNot, Delete m_subExpression->checkTypeRequirements(); - if (m_operator == Token::Value::INC || m_operator == Token::Value::DEC || m_operator == Token::Value::DELETE) + if (m_operator == Token::Value::Inc || m_operator == Token::Value::Dec || m_operator == Token::Value::Delete) m_subExpression->requireLValue(); - m_type = m_subExpression->getType(); - if (!m_type->acceptsUnaryOperator(m_operator)) + m_type = m_subExpression->getType()->unaryOperatorResult(m_operator); + if (!m_type) BOOST_THROW_EXCEPTION(createTypeError("Unary operator not compatible with type.")); } @@ -225,24 +482,13 @@ void BinaryOperation::checkTypeRequirements() { m_left->checkTypeRequirements(); m_right->checkTypeRequirements(); - if (m_right->getType()->isImplicitlyConvertibleTo(*m_left->getType())) - m_commonType = m_left->getType(); - else if (m_left->getType()->isImplicitlyConvertibleTo(*m_right->getType())) - m_commonType = m_right->getType(); - else - BOOST_THROW_EXCEPTION(createTypeError("No common type found in binary operation: " + - m_left->getType()->toString() + " vs. " + + m_commonType = m_left->getType()->binaryOperatorResult(m_operator, m_right->getType()); + if (!m_commonType) + BOOST_THROW_EXCEPTION(createTypeError("Operator " + string(Token::toString(m_operator)) + + " not compatible with types " + + m_left->getType()->toString() + " and " + m_right->getType()->toString())); - if (Token::isCompareOp(m_operator)) - m_type = make_shared<BoolType>(); - else - { - m_type = m_commonType; - if (!m_commonType->acceptsBinaryOperator(m_operator)) - BOOST_THROW_EXCEPTION(createTypeError("Operator " + string(Token::toString(m_operator)) + - " not compatible with type " + - m_commonType->toString())); - } + m_type = Token::isCompareOp(m_operator) ? make_shared<BoolType>() : m_commonType; } void FunctionCall::checkTypeRequirements() @@ -258,8 +504,9 @@ void FunctionCall::checkTypeRequirements() //@todo for structs, we have to check the number of arguments to be equal to the // number of non-mapping members if (m_arguments.size() != 1) - BOOST_THROW_EXCEPTION(createTypeError("More than one argument for " - "explicit type conersion.")); + BOOST_THROW_EXCEPTION(createTypeError("More than one argument for explicit type conversion.")); + if (!m_names.empty()) + BOOST_THROW_EXCEPTION(createTypeError("Type conversion cannot allow named arguments.")); if (!m_arguments.front()->getType()->isExplicitlyConvertibleTo(*type.getActualType())) BOOST_THROW_EXCEPTION(createTypeError("Explicit type conversion not allowed.")); m_type = type.getActualType(); @@ -270,11 +517,48 @@ void FunctionCall::checkTypeRequirements() // and then ask if that is implicitly convertible to the struct represented by the // function parameters TypePointers const& parameterTypes = functionType->getParameterTypes(); - if (parameterTypes.size() != m_arguments.size()) + if (!functionType->takesArbitraryParameters() && parameterTypes.size() != m_arguments.size()) BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for function call.")); - for (size_t i = 0; i < m_arguments.size(); ++i) - if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[i])) - BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in function call.")); + + if (m_names.empty()) + { + for (size_t i = 0; i < m_arguments.size(); ++i) + if (!functionType->takesArbitraryParameters() && + !m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[i])) + BOOST_THROW_EXCEPTION(m_arguments[i]->createTypeError("Invalid type for argument in function call.")); + } + else + { + if (functionType->takesArbitraryParameters()) + BOOST_THROW_EXCEPTION(createTypeError("Named arguments cannnot be used for functions " + "that take arbitrary parameters.")); + auto const& parameterNames = functionType->getParameterNames(); + if (parameterNames.size() != m_names.size()) + BOOST_THROW_EXCEPTION(createTypeError("Some argument names are missing.")); + + // check duplicate names + for (size_t i = 0; i < m_names.size(); i++) + for (size_t j = i + 1; j < m_names.size(); j++) + if (*m_names[i] == *m_names[j]) + BOOST_THROW_EXCEPTION(m_arguments[i]->createTypeError("Duplicate named argument.")); + + for (size_t i = 0; i < m_names.size(); i++) { + bool found = false; + for (size_t j = 0; j < parameterNames.size(); j++) { + if (parameterNames[j] == *m_names[i]) { + // check type convertible + if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[j])) + BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in function call.")); + + found = true; + break; + } + } + if (!found) + BOOST_THROW_EXCEPTION(createTypeError("Named argument does not match function declaration.")); + } + } + // @todo actually the return type should be an anonymous struct, // but we change it to the type of the first return value until we have structs if (functionType->getReturnParameterTypes().empty()) @@ -288,26 +572,19 @@ void FunctionCall::checkTypeRequirements() bool FunctionCall::isTypeConversion() const { - return m_expression->getType()->getCategory() == Type::Category::TYPE; + return m_expression->getType()->getCategory() == Type::Category::TypeType; } void NewExpression::checkTypeRequirements() { m_contractName->checkTypeRequirements(); - for (ASTPointer<Expression> const& argument: m_arguments) - argument->checkTypeRequirements(); - m_contract = dynamic_cast<ContractDefinition const*>(m_contractName->getReferencedDeclaration()); if (!m_contract) BOOST_THROW_EXCEPTION(createTypeError("Identifier is not a contract.")); - shared_ptr<ContractType const> type = make_shared<ContractType const>(*m_contract); - m_type = type; - TypePointers const& parameterTypes = type->getConstructorType()->getParameterTypes(); - if (parameterTypes.size() != m_arguments.size()) - BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for constructor call.")); - for (size_t i = 0; i < m_arguments.size(); ++i) - if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[i])) - BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in constructor call.")); + shared_ptr<ContractType const> contractType = make_shared<ContractType>(*m_contract); + TypePointers const& parameterTypes = contractType->getConstructorType()->getParameterTypes(); + m_type = make_shared<FunctionType>(parameterTypes, TypePointers{contractType}, + FunctionType::Location::Creation); } void MemberAccess::checkTypeRequirements() @@ -316,78 +593,43 @@ void MemberAccess::checkTypeRequirements() Type const& type = *m_expression->getType(); m_type = type.getMemberType(*m_memberName); if (!m_type) - BOOST_THROW_EXCEPTION(createTypeError("Member \"" + *m_memberName + "\" not found in " + type.toString())); - //@todo later, this will not always be STORAGE - m_lvalue = type.getCategory() == Type::Category::STRUCT ? LValueType::STORAGE : LValueType::NONE; + BOOST_THROW_EXCEPTION(createTypeError("Member \"" + *m_memberName + "\" not found or not " + "visible in " + type.toString())); + m_isLValue = (type.getCategory() == Type::Category::Struct); } void IndexAccess::checkTypeRequirements() { m_base->checkTypeRequirements(); - if (m_base->getType()->getCategory() != Type::Category::MAPPING) + if (m_base->getType()->getCategory() != Type::Category::Mapping) BOOST_THROW_EXCEPTION(m_base->createTypeError("Indexed expression has to be a mapping (is " + m_base->getType()->toString() + ")")); MappingType const& type = dynamic_cast<MappingType const&>(*m_base->getType()); m_index->expectType(*type.getKeyType()); m_type = type.getValueType(); - m_lvalue = LValueType::STORAGE; + m_isLValue = true; } void Identifier::checkTypeRequirements() { solAssert(m_referencedDeclaration, "Identifier not resolved."); - VariableDeclaration const* variable = dynamic_cast<VariableDeclaration const*>(m_referencedDeclaration); - if (variable) - { - if (!variable->getType()) - BOOST_THROW_EXCEPTION(createTypeError("Variable referenced before type could be determined.")); - m_type = variable->getType(); - m_lvalue = variable->isLocalVariable() ? LValueType::LOCAL : LValueType::STORAGE; - return; - } - //@todo can we unify these with TypeName::toType()? - StructDefinition const* structDef = dynamic_cast<StructDefinition const*>(m_referencedDeclaration); - if (structDef) - { - // note that we do not have a struct type here - m_type = make_shared<TypeType const>(make_shared<StructType const>(*structDef)); - return; - } - FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(m_referencedDeclaration); - if (functionDef) - { - // a function reference is not a TypeType, because calling a TypeType converts to the type. - // Calling a function (e.g. function(12), otherContract.function(34)) does not do a type - // conversion. - m_type = make_shared<FunctionType const>(*functionDef); - return; - } - ContractDefinition const* contractDef = dynamic_cast<ContractDefinition const*>(m_referencedDeclaration); - if (contractDef) - { - m_type = make_shared<TypeType const>(make_shared<ContractType>(*contractDef)); - return; - } - MagicVariableDeclaration const* magicVariable = dynamic_cast<MagicVariableDeclaration const*>(m_referencedDeclaration); - if (magicVariable) - { - m_type = magicVariable->getType(); - return; - } - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Declaration reference of unknown/forbidden type.")); + m_isLValue = m_referencedDeclaration->isLValue(); + m_type = m_referencedDeclaration->getType(m_currentContract); + if (!m_type) + BOOST_THROW_EXCEPTION(createTypeError("Declaration referenced before type could be determined.")); } void ElementaryTypeNameExpression::checkTypeRequirements() { - m_type = make_shared<TypeType const>(Type::fromElementaryTypeName(m_typeToken)); + m_type = make_shared<TypeType>(Type::fromElementaryTypeName(m_typeToken)); } void Literal::checkTypeRequirements() { m_type = Type::forLiteral(*this); if (!m_type) - BOOST_THROW_EXCEPTION(createTypeError("Literal value too large.")); + BOOST_THROW_EXCEPTION(createTypeError("Invalid literal value.")); } } @@ -132,68 +132,167 @@ private: class Declaration: public ASTNode { public: - Declaration(Location const& _location, ASTPointer<ASTString> const& _name): - ASTNode(_location), m_name(_name), m_scope(nullptr) {} + /// Visibility ordered from restricted to unrestricted. + enum class Visibility { Default, Private, Inheritable, Public, External }; + + Declaration(Location const& _location, ASTPointer<ASTString> const& _name, + Visibility _visibility = Visibility::Default): + ASTNode(_location), m_name(_name), m_visibility(_visibility), m_scope(nullptr) {} /// @returns the declared name. ASTString const& getName() const { return *m_name; } + Visibility getVisibility() const { return m_visibility == Visibility::Default ? getDefaultVisibility() : m_visibility; } + bool isPublic() const { return getVisibility() >= Visibility::Public; } + bool isVisibleInContract() const { return getVisibility() != Visibility::External; } + bool isVisibleInDerivedContracts() const { return isVisibleInContract() && getVisibility() >= Visibility::Inheritable; } + /// @returns the scope this declaration resides in. Can be nullptr if it is the global scope. /// Available only after name and type resolution step. Declaration const* getScope() const { return m_scope; } void setScope(Declaration const* _scope) { m_scope = _scope; } + /// @returns the type of expressions referencing this declaration. + /// The current contract has to be given since this context can change the type, especially of + /// contract types. + virtual TypePointer getType(ContractDefinition const* m_currentContract = nullptr) const = 0; + virtual bool isLValue() const { return false; } + +protected: + virtual Visibility getDefaultVisibility() const { return Visibility::Public; } + private: ASTPointer<ASTString> m_name; + Visibility m_visibility; Declaration const* m_scope; }; /** + * Abstract class that is added to each AST node that can store local variables. + */ +class VariableScope +{ +public: + void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); } + std::vector<VariableDeclaration const*> const& getLocalVariables() const { return m_localVariables; } + +private: + std::vector<VariableDeclaration const*> m_localVariables; +}; + +/** + * Abstract class that is added to each AST node that can receive documentation. + */ +class Documented +{ +public: + explicit Documented(ASTPointer<ASTString> const& _documentation): m_documentation(_documentation) {} + + /// @return A shared pointer of an ASTString. + /// Can contain a nullptr in which case indicates absence of documentation + ASTPointer<ASTString> const& getDocumentation() const { return m_documentation; } + +protected: + ASTPointer<ASTString> m_documentation; +}; + +/// @} + +/** * Definition of a contract. This is the only AST nodes where child nodes are not visited in * document order. It first visits all struct declarations, then all variable declarations and * finally all function declarations. */ -class ContractDefinition: public Declaration +class ContractDefinition: public Declaration, public Documented { public: ContractDefinition(Location const& _location, ASTPointer<ASTString> const& _name, ASTPointer<ASTString> const& _documentation, + std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts, std::vector<ASTPointer<StructDefinition>> const& _definedStructs, + std::vector<ASTPointer<EnumDefinition>> const& _definedEnums, std::vector<ASTPointer<VariableDeclaration>> const& _stateVariables, - std::vector<ASTPointer<FunctionDefinition>> const& _definedFunctions): - Declaration(_location, _name), + std::vector<ASTPointer<FunctionDefinition>> const& _definedFunctions, + std::vector<ASTPointer<ModifierDefinition>> const& _functionModifiers, + std::vector<ASTPointer<EventDefinition>> const& _events): + Declaration(_location, _name), Documented(_documentation), + m_baseContracts(_baseContracts), m_definedStructs(_definedStructs), + m_definedEnums(_definedEnums), m_stateVariables(_stateVariables), m_definedFunctions(_definedFunctions), - m_documentation(_documentation) + m_functionModifiers(_functionModifiers), + m_events(_events) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; + std::vector<ASTPointer<InheritanceSpecifier>> const& getBaseContracts() const { return m_baseContracts; } std::vector<ASTPointer<StructDefinition>> const& getDefinedStructs() const { return m_definedStructs; } + std::vector<ASTPointer<EnumDefinition>> const& getDefinedEnums() const { return m_definedEnums; } std::vector<ASTPointer<VariableDeclaration>> const& getStateVariables() const { return m_stateVariables; } + std::vector<ASTPointer<ModifierDefinition>> const& getFunctionModifiers() const { return m_functionModifiers; } std::vector<ASTPointer<FunctionDefinition>> const& getDefinedFunctions() const { return m_definedFunctions; } + std::vector<ASTPointer<EventDefinition>> const& getEvents() const { return m_events; } + std::vector<ASTPointer<EventDefinition>> const& getInterfaceEvents() const; + + virtual TypePointer getType(ContractDefinition const* m_currentContract) const override; - /// Checks that the constructor does not have a "returns" statement and calls - /// checkTypeRequirements on all its functions. + /// Checks that there are no illegal overrides, that the constructor does not have a "returns" + /// and calls checkTypeRequirements on all its functions. void checkTypeRequirements(); - /// @return A shared pointer of an ASTString. - /// Can contain a nullptr in which case indicates absence of documentation - ASTPointer<ASTString> const& getDocumentation() const { return m_documentation; } + /// @returns a map of canonical function signatures to FunctionDefinitions + /// as intended for use by the ABI. + std::map<FixedHash<4>, FunctionTypePointer> getInterfaceFunctions() const; - /// Returns the functions that make up the calling interface in the intended order. - std::vector<FunctionDefinition const*> getInterfaceFunctions() const; + /// List of all (direct and indirect) base contracts in order from derived to base, including + /// the contract itself. Available after name resolution + std::vector<ContractDefinition const*> const& getLinearizedBaseContracts() const { return m_linearizedBaseContracts; } + void setLinearizedBaseContracts(std::vector<ContractDefinition const*> const& _bases) { m_linearizedBaseContracts = _bases; } - /// Returns the constructor or nullptr if no constructor was specified + /// Returns the constructor or nullptr if no constructor was specified. FunctionDefinition const* getConstructor() const; + /// Returns the fallback function or nullptr if no fallback function was specified. + FunctionDefinition const* getFallbackFunction() const; private: + void checkIllegalOverrides() const; + + std::vector<std::pair<FixedHash<4>, FunctionTypePointer>> const& getInterfaceFunctionList() const; + + std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts; std::vector<ASTPointer<StructDefinition>> m_definedStructs; + std::vector<ASTPointer<EnumDefinition>> m_definedEnums; std::vector<ASTPointer<VariableDeclaration>> m_stateVariables; std::vector<ASTPointer<FunctionDefinition>> m_definedFunctions; - ASTPointer<ASTString> m_documentation; + std::vector<ASTPointer<ModifierDefinition>> m_functionModifiers; + std::vector<ASTPointer<EventDefinition>> m_events; + + std::vector<ContractDefinition const*> m_linearizedBaseContracts; + mutable std::unique_ptr<std::vector<std::pair<FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList; + mutable std::unique_ptr<std::vector<ASTPointer<EventDefinition>>> m_interfaceEvents; +}; + +class InheritanceSpecifier: public ASTNode +{ +public: + InheritanceSpecifier(Location const& _location, ASTPointer<Identifier> const& _baseName, + std::vector<ASTPointer<Expression>> _arguments): + ASTNode(_location), m_baseName(_baseName), m_arguments(_arguments) {} + + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + ASTPointer<Identifier> const& getName() const { return m_baseName; } + std::vector<ASTPointer<Expression>> const& getArguments() const { return m_arguments; } + + void checkTypeRequirements(); + +private: + ASTPointer<Identifier> m_baseName; + std::vector<ASTPointer<Expression>> m_arguments; }; class StructDefinition: public Declaration @@ -208,6 +307,8 @@ public: std::vector<ASTPointer<VariableDeclaration>> const& getMembers() const { return m_members; } + virtual TypePointer getType(ContractDefinition const*) const override; + /// Checks that the members do not include any recursive structs and have valid types /// (e.g. no functions). void checkValidityOfMembers() const; @@ -219,6 +320,39 @@ private: std::vector<ASTPointer<VariableDeclaration>> m_members; }; +class EnumDefinition: public Declaration +{ +public: + EnumDefinition(Location const& _location, + ASTPointer<ASTString> const& _name, + std::vector<ASTPointer<EnumValue>> const& _members): + Declaration(_location, _name), m_members(_members) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + std::vector<ASTPointer<EnumValue>> const& getMembers() const { return m_members; } + + virtual TypePointer getType(ContractDefinition const*) const override; + +private: + std::vector<ASTPointer<EnumValue>> m_members; +}; + +/** + * Declaration of an Enum Value + */ +class EnumValue: public Declaration +{ + public: + EnumValue(Location const& _location, + ASTPointer<ASTString> const& _name): + Declaration(_location, _name) {} + + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + TypePointer getType(ContractDefinition const* = nullptr) const; +}; + /** * Parameter list, used as function parameter list and return list. * None of the parameters is allowed to contain mappings (not even recursively @@ -239,53 +373,55 @@ private: std::vector<ASTPointer<VariableDeclaration>> m_parameters; }; -class FunctionDefinition: public Declaration +class FunctionDefinition: public Declaration, public VariableScope, public Documented { public: FunctionDefinition(Location const& _location, ASTPointer<ASTString> const& _name, - bool _isPublic, + Declaration::Visibility _visibility, bool _isConstructor, ASTPointer<ASTString> const& _documentation, ASTPointer<ParameterList> const& _parameters, bool _isDeclaredConst, + std::vector<ASTPointer<ModifierInvocation>> const& _modifiers, ASTPointer<ParameterList> const& _returnParameters, ASTPointer<Block> const& _body): - Declaration(_location, _name), m_isPublic(_isPublic), + Declaration(_location, _name, _visibility), Documented(_documentation), + m_isConstructor(_isConstructor), m_parameters(_parameters), m_isDeclaredConst(_isDeclaredConst), + m_functionModifiers(_modifiers), m_returnParameters(_returnParameters), - m_body(_body), - m_documentation(_documentation) + m_body(_body) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; - bool isPublic() const { return m_isPublic; } + bool isConstructor() const { return m_isConstructor; } bool isDeclaredConst() const { return m_isDeclaredConst; } + std::vector<ASTPointer<ModifierInvocation>> const& getModifiers() const { return m_functionModifiers; } std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters->getParameters(); } ParameterList const& getParameterList() const { return *m_parameters; } std::vector<ASTPointer<VariableDeclaration>> const& getReturnParameters() const { return m_returnParameters->getParameters(); } ASTPointer<ParameterList> const& getReturnParameterList() const { return m_returnParameters; } Block const& getBody() const { return *m_body; } - /// @return A shared pointer of an ASTString. - /// Can contain a nullptr in which case indicates absence of documentation - ASTPointer<ASTString> const& getDocumentation() const { return m_documentation; } - void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); } - std::vector<VariableDeclaration const*> const& getLocalVariables() const { return m_localVariables; } + virtual TypePointer getType(ContractDefinition const*) const override; /// Checks that all parameters have allowed types and calls checkTypeRequirements on the body. void checkTypeRequirements(); + /// @returns the canonical signature of the function + /// That consists of the name of the function followed by the types of the + /// arguments separated by commas all enclosed in parentheses without any spaces. + std::string getCanonicalSignature() const; + private: - bool m_isPublic; + bool m_isConstructor; ASTPointer<ParameterList> m_parameters; bool m_isDeclaredConst; + std::vector<ASTPointer<ModifierInvocation>> m_functionModifiers; ASTPointer<ParameterList> m_returnParameters; ASTPointer<Block> m_body; - ASTPointer<ASTString> m_documentation; - - std::vector<VariableDeclaration const*> m_localVariables; }; /** @@ -296,8 +432,10 @@ class VariableDeclaration: public Declaration { public: VariableDeclaration(Location const& _location, ASTPointer<TypeName> const& _type, - ASTPointer<ASTString> const& _name): - Declaration(_location, _name), m_typeName(_type) {} + ASTPointer<ASTString> const& _name, Visibility _visibility, + bool _isStateVar = false, bool _isIndexed = false): + Declaration(_location, _name, _visibility), m_typeName(_type), + m_isStateVariable(_isStateVar), m_isIndexed(_isIndexed) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; @@ -305,18 +443,109 @@ public: /// Returns the declared or inferred type. Can be an empty pointer if no type was explicitly /// declared and there is no assignment to the variable that fixes the type. - std::shared_ptr<Type const> const& getType() const { return m_type; } + TypePointer getType(ContractDefinition const* = nullptr) const { return m_type; } void setType(std::shared_ptr<Type const> const& _type) { m_type = _type; } + virtual bool isLValue() const override; bool isLocalVariable() const { return !!dynamic_cast<FunctionDefinition const*>(getScope()); } + bool isExternalFunctionParameter() const; + bool isStateVariable() const { return m_isStateVariable; } + bool isIndexed() const { return m_isIndexed; } + +protected: + Visibility getDefaultVisibility() const override { return Visibility::Inheritable; } private: - ASTPointer<TypeName> m_typeName; ///< can be empty ("var") + ASTPointer<TypeName> m_typeName; ///< can be empty ("var") + bool m_isStateVariable; ///< Whether or not this is a contract state variable + bool m_isIndexed; ///< Whether this is an indexed variable (used by events). std::shared_ptr<Type const> m_type; ///< derived type, initially empty }; /** + * Definition of a function modifier. + */ +class ModifierDefinition: public Declaration, public VariableScope, public Documented +{ +public: + ModifierDefinition(Location const& _location, + ASTPointer<ASTString> const& _name, + ASTPointer<ASTString> const& _documentation, + ASTPointer<ParameterList> const& _parameters, + ASTPointer<Block> const& _body): + Declaration(_location, _name), Documented(_documentation), + m_parameters(_parameters), m_body(_body) {} + + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters->getParameters(); } + ParameterList const& getParameterList() const { return *m_parameters; } + Block const& getBody() const { return *m_body; } + + virtual TypePointer getType(ContractDefinition const* = nullptr) const override; + + void checkTypeRequirements(); + +private: + ASTPointer<ParameterList> m_parameters; + ASTPointer<Block> m_body; +}; + +/** + * Invocation/usage of a modifier in a function header. + */ +class ModifierInvocation: public ASTNode +{ +public: + ModifierInvocation(Location const& _location, ASTPointer<Identifier> const& _name, + std::vector<ASTPointer<Expression>> _arguments): + ASTNode(_location), m_modifierName(_name), m_arguments(_arguments) {} + + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + ASTPointer<Identifier> const& getName() const { return m_modifierName; } + std::vector<ASTPointer<Expression>> const& getArguments() const { return m_arguments; } + + void checkTypeRequirements(); + +private: + ASTPointer<Identifier> m_modifierName; + std::vector<ASTPointer<Expression>> m_arguments; +}; + +/** + * Definition of a (loggable) event. + */ +class EventDefinition: public Declaration, public VariableScope, public Documented +{ +public: + EventDefinition(Location const& _location, + ASTPointer<ASTString> const& _name, + ASTPointer<ASTString> const& _documentation, + ASTPointer<ParameterList> const& _parameters): + Declaration(_location, _name), Documented(_documentation), m_parameters(_parameters) {} + + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters->getParameters(); } + ParameterList const& getParameterList() const { return *m_parameters; } + + virtual TypePointer getType(ContractDefinition const* = nullptr) const override + { + return std::make_shared<FunctionType>(*this); + } + + void checkTypeRequirements(); + +private: + ASTPointer<ParameterList> m_parameters; +}; + +/** * Pseudo AST node that is used as declaration for "this", "msg", "tx", "block" and the global * functions when such an identifier is encountered. Will never have a valid location in the source code. */ @@ -330,7 +559,7 @@ public: virtual void accept(ASTConstVisitor&) const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("MagicVariableDeclaration used inside real AST.")); } - std::shared_ptr<Type const> const& getType() const { return m_type; } + virtual TypePointer getType(ContractDefinition const* = nullptr) const override { return m_type; } private: std::shared_ptr<Type const> m_type; @@ -458,6 +687,21 @@ private: }; /** + * Special placeholder statement denoted by "_" used in function modifiers. This is replaced by + * the original function when the modifier is applied. + */ +class PlaceholderStatement: public Statement +{ +public: + PlaceholderStatement(Location const& _location): Statement(_location) {} + + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + virtual void checkTypeRequirements() override { } +}; + +/** * If-statement with an optional "else" part. Note that "else if" is modeled by having a new * if-statement as the false (else) body. */ @@ -474,7 +718,7 @@ public: Expression const& getCondition() const { return *m_condition; } Statement const& getTrueStatement() const { return *m_trueBody; } - /// @returns the "else" part of the if statement or nullptr if there is no "else" part. + /// @returns the "else" part of the if statement or nullptr if there is no "else" part. Statement const* getFalseStatement() const { return m_falseBody.get(); } private: @@ -573,19 +817,15 @@ public: virtual void accept(ASTConstVisitor& _visitor) const override; virtual void checkTypeRequirements() override; - void setFunctionReturnParameters(ParameterList& _parameters) { m_returnParameters = &_parameters; } - ParameterList const& getFunctionReturnParameters() const - { - solAssert(m_returnParameters, ""); - return *m_returnParameters; - } + void setFunctionReturnParameters(ParameterList const* _parameters) { m_returnParameters = _parameters; } + ParameterList const* getFunctionReturnParameters() const { return m_returnParameters; } Expression const* getExpression() const { return m_expression.get(); } private: ASTPointer<Expression> m_expression; ///< value to return, optional /// Pointer to the parameter list of the function, filled by the @ref NameAndTypeResolver. - ParameterList* m_returnParameters; + ParameterList const* m_returnParameters; }; /** @@ -641,16 +881,12 @@ private: */ class Expression: public ASTNode { -protected: - enum class LValueType { NONE, LOCAL, STORAGE }; - public: - Expression(Location const& _location): ASTNode(_location), m_lvalue(LValueType::NONE), m_lvalueRequested(false) {} + Expression(Location const& _location): ASTNode(_location) {} virtual void checkTypeRequirements() = 0; std::shared_ptr<Type const> const& getType() const { return m_type; } - bool isLValue() const { return m_lvalue != LValueType::NONE; } - bool isLocalLValue() const { return m_lvalue == LValueType::LOCAL; } + bool isLValue() const { return m_isLValue; } /// Helper function, infer the type via @ref checkTypeRequirements and then check that it /// is implicitly convertible to @a _expectedType. If not, throw exception. @@ -665,11 +901,11 @@ public: protected: //! Inferred type of the expression, only filled after a call to checkTypeRequirements(). std::shared_ptr<Type const> m_type; - //! If this expression is an lvalue (i.e. something that can be assigned to) and is stored - //! locally or in storage. This is set during calls to @a checkTypeRequirements() - LValueType m_lvalue; + //! If this expression is an lvalue (i.e. something that can be assigned to). + //! This is set during calls to @a checkTypeRequirements() + bool m_isLValue = false; //! Whether the outer expression requested the address (true) or the value (false) of this expression. - bool m_lvalueRequested; + bool m_lvalueRequested = false; }; /// Assignment, can also be a compound assignment. @@ -765,14 +1001,15 @@ class FunctionCall: public Expression { public: FunctionCall(Location const& _location, ASTPointer<Expression> const& _expression, - std::vector<ASTPointer<Expression>> const& _arguments): - Expression(_location), m_expression(_expression), m_arguments(_arguments) {} + std::vector<ASTPointer<Expression>> const& _arguments, std::vector<ASTPointer<ASTString>> const& _names): + Expression(_location), m_expression(_expression), m_arguments(_arguments), m_names(_names) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; virtual void checkTypeRequirements() override; Expression const& getExpression() const { return *m_expression; } std::vector<ASTPointer<Expression const>> getArguments() const { return {m_arguments.begin(), m_arguments.end()}; } + std::vector<ASTPointer<ASTString>> const& getNames() const { return m_names; } /// Returns true if this is not an actual function call, but an explicit type conversion /// or constructor call. @@ -781,29 +1018,26 @@ public: private: ASTPointer<Expression> m_expression; std::vector<ASTPointer<Expression>> m_arguments; + std::vector<ASTPointer<ASTString>> m_names; }; /** - * Expression that creates a new contract, e.g. "new SomeContract(1, 2)". + * Expression that creates a new contract, e.g. the "new SomeContract" part in "new SomeContract(1, 2)". */ class NewExpression: public Expression { public: - NewExpression(Location const& _location, ASTPointer<Identifier> const& _contractName, - std::vector<ASTPointer<Expression>> const& _arguments): - Expression(_location), m_contractName(_contractName), m_arguments(_arguments) {} + NewExpression(Location const& _location, ASTPointer<Identifier> const& _contractName): + Expression(_location), m_contractName(_contractName) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; virtual void checkTypeRequirements() override; - std::vector<ASTPointer<Expression const>> getArguments() const { return {m_arguments.begin(), m_arguments.end()}; } - /// Returns the referenced contract. Can only be called after type checking. ContractDefinition const* getContract() const { solAssert(m_contract, ""); return m_contract; } private: ASTPointer<Identifier> m_contractName; - std::vector<ASTPointer<Expression>> m_arguments; ContractDefinition const* m_contract = nullptr; }; @@ -866,21 +1100,30 @@ class Identifier: public PrimaryExpression { public: Identifier(Location const& _location, ASTPointer<ASTString> const& _name): - PrimaryExpression(_location), m_name(_name), m_referencedDeclaration(nullptr) {} + PrimaryExpression(_location), m_name(_name) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; virtual void checkTypeRequirements() override; ASTString const& getName() const { return *m_name; } - void setReferencedDeclaration(Declaration const& _referencedDeclaration) { m_referencedDeclaration = &_referencedDeclaration; } + void setReferencedDeclaration(Declaration const& _referencedDeclaration, + ContractDefinition const* _currentContract = nullptr) + { + m_referencedDeclaration = &_referencedDeclaration; + m_currentContract = _currentContract; + } Declaration const* getReferencedDeclaration() const { return m_referencedDeclaration; } + ContractDefinition const* getCurrentContract() const { return m_currentContract; } private: ASTPointer<ASTString> m_name; /// Declaration the name refers to. - Declaration const* m_referencedDeclaration; + Declaration const* m_referencedDeclaration = nullptr; + /// Stores a reference to the current contract. This is needed because types of base contracts + /// change depending on the context. + ContractDefinition const* m_currentContract = nullptr; }; /** @@ -907,13 +1150,23 @@ private: }; /** - * A literal string or number. @see Type::literalToBigEndian is used to actually parse its value. + * A literal string or number. @see ExpressionCompiler::endVisit() is used to actually parse its value. */ class Literal: public PrimaryExpression { public: - Literal(Location const& _location, Token::Value _token, ASTPointer<ASTString> const& _value): - PrimaryExpression(_location), m_token(_token), m_value(_value) {} + enum class SubDenomination + { + None = Token::Illegal, + Wei = Token::SubWei, + Szabo = Token::SubSzabo, + Finney = Token::SubFinney, + Ether = Token::SubEther + }; + Literal(Location const& _location, Token::Value _token, + ASTPointer<ASTString> const& _value, + SubDenomination _sub = SubDenomination::None): + PrimaryExpression(_location), m_token(_token), m_value(_value), m_subDenomination(_sub) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; virtual void checkTypeRequirements() override; @@ -922,12 +1175,16 @@ public: /// @returns the non-parsed value of the literal ASTString const& getValue() const { return *m_value; } + SubDenomination getSubDenomination() const { return m_subDenomination; } + private: Token::Value m_token; ASTPointer<ASTString> m_value; + SubDenomination m_subDenomination; }; /// @} + } } diff --git a/ASTForward.h b/ASTForward.h index c960fc8f..0b6817e4 100644 --- a/ASTForward.h +++ b/ASTForward.h @@ -38,10 +38,16 @@ class SourceUnit; class ImportDirective; class Declaration; class ContractDefinition; +class InheritanceSpecifier; class StructDefinition; +class EnumDefinition; +class EnumValue; class ParameterList; class FunctionDefinition; class VariableDeclaration; +class ModifierDefinition; +class ModifierInvocation; +class EventDefinition; class MagicVariableDeclaration; class TypeName; class ElementaryTypeName; @@ -49,6 +55,7 @@ class UserDefinedTypeName; class Mapping; class Statement; class Block; +class PlaceholderStatement; class IfStatement; class BreakableStatement; class WhileStatement; @@ -71,6 +78,8 @@ class Identifier; class ElementaryTypeNameExpression; class Literal; +class VariableScope; + // Used as pointers to AST nodes, to be replaced by more clever pointers, e.g. pointers which do // not do reference counting but point to a special memory area that is completely released // explicitly. diff --git a/ASTJsonConverter.cpp b/ASTJsonConverter.cpp new file mode 100644 index 00000000..04feafe2 --- /dev/null +++ b/ASTJsonConverter.cpp @@ -0,0 +1,469 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Lefteris <lefteris@ethdev.com> + * @date 2015 + * Converts the AST into json format + */ + +#include <libsolidity/ASTJsonConverter.h> +#include <libsolidity/AST.h> + +using namespace std; + +namespace dev +{ +namespace solidity +{ + +void ASTJsonConverter::addKeyValue(Json::Value& _obj, string const& _key, string const& _val) +{ + // special handling for booleans + if (_key == "const" || _key == "public" || _key == "local" || + _key == "lvalue" || _key == "local_lvalue" || _key == "prefix") + _obj[_key] = (_val == "1") ? true : false; + else + // else simply add it as a string + _obj[_key] = _val; +} + +void ASTJsonConverter::addJsonNode(string const& _nodeName, + initializer_list<pair<string const, string const>> _list, + bool _hasChildren = false) +{ + Json::Value node; + + node["name"] = _nodeName; + if (_list.size() != 0) + { + Json::Value attrs; + for (auto& e: _list) + addKeyValue(attrs, e.first, e.second); + node["attributes"] = attrs; + } + + m_jsonNodePtrs.top()->append(node); + + if (_hasChildren) + { + Json::Value& addedNode = (*m_jsonNodePtrs.top())[m_jsonNodePtrs.top()->size() - 1]; + Json::Value children(Json::arrayValue); + addedNode["children"] = children; + m_jsonNodePtrs.push(&addedNode["children"]); + } +} + +ASTJsonConverter::ASTJsonConverter(ASTNode const& _ast): m_ast(&_ast) +{ + Json::Value children(Json::arrayValue); + + m_astJson["name"] = "root"; + m_astJson["children"] = children; + m_jsonNodePtrs.push(&m_astJson["children"]); +} + +void ASTJsonConverter::print(ostream& _stream) +{ + m_ast->accept(*this); + _stream << m_astJson; +} + +bool ASTJsonConverter::visit(ImportDirective const& _node) +{ + addJsonNode("Import", { make_pair("file", _node.getIdentifier())}); + return true; +} + +bool ASTJsonConverter::visit(ContractDefinition const& _node) +{ + addJsonNode("Contract", { make_pair("name", _node.getName()) }, true); + return true; +} + +bool ASTJsonConverter::visit(StructDefinition const& _node) +{ + addJsonNode("Struct", { make_pair("name", _node.getName()) }, true); + return true; +} + +bool ASTJsonConverter::visit(ParameterList const&) +{ + addJsonNode("ParameterList", {}, true); + return true; +} + +bool ASTJsonConverter::visit(FunctionDefinition const& _node) +{ + addJsonNode("Function", + { make_pair("name", _node.getName()), + make_pair("public", boost::lexical_cast<std::string>(_node.isPublic())), + make_pair("const", boost::lexical_cast<std::string>(_node.isDeclaredConst())) }, + true); + return true; +} + +bool ASTJsonConverter::visit(VariableDeclaration const& _node) +{ + addJsonNode("VariableDeclaration", { make_pair("name", _node.getName()) }, true); + return true; +} + +bool ASTJsonConverter::visit(TypeName const&) +{ + return true; +} + +bool ASTJsonConverter::visit(ElementaryTypeName const& _node) +{ + addJsonNode("ElementaryTypeName", { make_pair("name", Token::toString(_node.getTypeName())) }); + return true; +} + +bool ASTJsonConverter::visit(UserDefinedTypeName const& _node) +{ + addJsonNode("UserDefinedTypeName", { make_pair("name", _node.getName()) }); + return true; +} + +bool ASTJsonConverter::visit(Mapping const&) +{ + addJsonNode("Mapping", {}, true); + return true; +} + +bool ASTJsonConverter::visit(Statement const&) +{ + addJsonNode("Statement", {}, true); + return true; +} + +bool ASTJsonConverter::visit(Block const&) +{ + addJsonNode("Block", {}, true); + return true; +} + +bool ASTJsonConverter::visit(IfStatement const&) +{ + addJsonNode("IfStatement", {}, true); + return true; +} + +bool ASTJsonConverter::visit(BreakableStatement const&) +{ + return true; +} + +bool ASTJsonConverter::visit(WhileStatement const&) +{ + addJsonNode("WhileStatement", {}, true); + return true; +} + +bool ASTJsonConverter::visit(ForStatement const&) +{ + addJsonNode("ForStatement", {}, true); + return true; +} + +bool ASTJsonConverter::visit(Continue const&) +{ + addJsonNode("Continue", {}); + return true; +} + +bool ASTJsonConverter::visit(Break const&) +{ + addJsonNode("Break", {}); + return true; +} + +bool ASTJsonConverter::visit(Return const&) +{ + addJsonNode("Return", {}, true);; + return true; +} + +bool ASTJsonConverter::visit(VariableDefinition const&) +{ + addJsonNode("VariableDefinition", {}, true); + return true; +} + +bool ASTJsonConverter::visit(ExpressionStatement const&) +{ + addJsonNode("ExpressionStatement", {}, true); + return true; +} + +bool ASTJsonConverter::visit(Expression const& _node) +{ + addJsonNode( + "Expression", + { make_pair("type", getType(_node)), + make_pair("lvalue", boost::lexical_cast<std::string>(_node.isLValue())) }, + true + ); + return true; +} + +bool ASTJsonConverter::visit(Assignment const& _node) +{ + addJsonNode("Assignment", + { make_pair("operator", Token::toString(_node.getAssignmentOperator())), + make_pair("type", getType(_node)) }, + true); + return true; +} + +bool ASTJsonConverter::visit(UnaryOperation const& _node) +{ + addJsonNode("UnaryOperation", + { make_pair("prefix", boost::lexical_cast<std::string>(_node.isPrefixOperation())), + make_pair("operator", Token::toString(_node.getOperator())), + make_pair("type", getType(_node)) }, + true); + return true; +} + +bool ASTJsonConverter::visit(BinaryOperation const& _node) +{ + addJsonNode("BinaryOperation", + { make_pair("operator", Token::toString(_node.getOperator())), + make_pair("type", getType(_node))}, + true); + return true; +} + +bool ASTJsonConverter::visit(FunctionCall const& _node) +{ + addJsonNode("FunctionCall", + { make_pair("type_conversion", boost::lexical_cast<std::string>(_node.isTypeConversion())), + make_pair("type", getType(_node)) }, + true); + return true; +} + +bool ASTJsonConverter::visit(NewExpression const& _node) +{ + addJsonNode("NewExpression", { make_pair("type", getType(_node)) }, true); + return true; +} + +bool ASTJsonConverter::visit(MemberAccess const& _node) +{ + addJsonNode("MemberAccess", + { make_pair("member_name", _node.getMemberName()), + make_pair("type", getType(_node)) }, + true); + return true; +} + +bool ASTJsonConverter::visit(IndexAccess const& _node) +{ + addJsonNode("IndexAccess", { make_pair("type", getType(_node)) }, true); + return true; +} + +bool ASTJsonConverter::visit(PrimaryExpression const&) +{ + return true; +} + +bool ASTJsonConverter::visit(Identifier const& _node) +{ + addJsonNode("Identifier", + { make_pair("value", _node.getName()), make_pair("type", getType(_node)) }); + return true; +} + +bool ASTJsonConverter::visit(ElementaryTypeNameExpression const& _node) +{ + addJsonNode("ElementaryTypenameExpression", + { make_pair("value", Token::toString(_node.getTypeToken())), make_pair("type", getType(_node)) }); + return true; +} + +bool ASTJsonConverter::visit(Literal const& _node) +{ + char const* tokenString = Token::toString(_node.getToken()); + addJsonNode("Literal", + { make_pair("string", (tokenString) ? tokenString : "null"), + make_pair("value", _node.getValue()), + make_pair("type", getType(_node)) }); + return true; +} + +void ASTJsonConverter::endVisit(ImportDirective const&) +{ +} + +void ASTJsonConverter::endVisit(ContractDefinition const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(StructDefinition const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(ParameterList const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(FunctionDefinition const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(VariableDeclaration const&) +{ +} + +void ASTJsonConverter::endVisit(TypeName const&) +{ +} + +void ASTJsonConverter::endVisit(ElementaryTypeName const&) +{ +} + +void ASTJsonConverter::endVisit(UserDefinedTypeName const&) +{ +} + +void ASTJsonConverter::endVisit(Mapping const&) +{ +} + +void ASTJsonConverter::endVisit(Statement const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(Block const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(IfStatement const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(BreakableStatement const&) +{ +} + +void ASTJsonConverter::endVisit(WhileStatement const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(ForStatement const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(Continue const&) +{ +} + +void ASTJsonConverter::endVisit(Break const&) +{ +} + +void ASTJsonConverter::endVisit(Return const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(VariableDefinition const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(ExpressionStatement const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(Expression const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(Assignment const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(UnaryOperation const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(BinaryOperation const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(FunctionCall const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(NewExpression const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(MemberAccess const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(IndexAccess const&) +{ + goUp(); +} + +void ASTJsonConverter::endVisit(PrimaryExpression const&) +{ +} + +void ASTJsonConverter::endVisit(Identifier const&) +{ +} + +void ASTJsonConverter::endVisit(ElementaryTypeNameExpression const&) +{ +} + +void ASTJsonConverter::endVisit(Literal const&) +{ +} + +string ASTJsonConverter::getType(Expression const& _expression) +{ + return (_expression.getType()) ? _expression.getType()->toString() : "Unknown"; +} + +} +} diff --git a/ASTJsonConverter.h b/ASTJsonConverter.h new file mode 100644 index 00000000..466801e9 --- /dev/null +++ b/ASTJsonConverter.h @@ -0,0 +1,135 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Lefteris <lefteris@ethdev.com> + * @date 2015 + * Converts the AST into json format + */ + +#pragma once + +#include <ostream> +#include <stack> +#include <libsolidity/ASTVisitor.h> +#include <libsolidity/Exceptions.h> +#include <libsolidity/Utils.h> +#include <json/json.h> + +namespace dev +{ +namespace solidity +{ + +/** + * Converter of the AST into JSON format + */ +class ASTJsonConverter: public ASTConstVisitor +{ +public: + /// Create a converter to JSON for the given abstract syntax tree. + ASTJsonConverter(ASTNode const& _ast); + /// Output the json representation of the AST to _stream. + void print(std::ostream& _stream); + + bool visit(ImportDirective const& _node) override; + bool visit(ContractDefinition const& _node) override; + bool visit(StructDefinition const& _node) override; + bool visit(ParameterList const& _node) override; + bool visit(FunctionDefinition const& _node) override; + bool visit(VariableDeclaration const& _node) override; + bool visit(TypeName const& _node) override; + bool visit(ElementaryTypeName const& _node) override; + bool visit(UserDefinedTypeName const& _node) override; + bool visit(Mapping const& _node) override; + bool visit(Statement const& _node) override; + bool visit(Block const& _node) override; + bool visit(IfStatement const& _node) override; + bool visit(BreakableStatement const& _node) override; + bool visit(WhileStatement const& _node) override; + bool visit(ForStatement const& _node) override; + bool visit(Continue const& _node) override; + bool visit(Break const& _node) override; + bool visit(Return const& _node) override; + bool visit(VariableDefinition const& _node) override; + bool visit(ExpressionStatement const& _node) override; + bool visit(Expression const& _node) override; + bool visit(Assignment const& _node) override; + bool visit(UnaryOperation const& _node) override; + bool visit(BinaryOperation const& _node) override; + bool visit(FunctionCall const& _node) override; + bool visit(NewExpression const& _node) override; + bool visit(MemberAccess const& _node) override; + bool visit(IndexAccess const& _node) override; + bool visit(PrimaryExpression const& _node) override; + bool visit(Identifier const& _node) override; + bool visit(ElementaryTypeNameExpression const& _node) override; + bool visit(Literal const& _node) override; + + void endVisit(ImportDirective const&) override; + void endVisit(ContractDefinition const&) override; + void endVisit(StructDefinition const&) override; + void endVisit(ParameterList const&) override; + void endVisit(FunctionDefinition const&) override; + void endVisit(VariableDeclaration const&) override; + void endVisit(TypeName const&) override; + void endVisit(ElementaryTypeName const&) override; + void endVisit(UserDefinedTypeName const&) override; + void endVisit(Mapping const&) override; + void endVisit(Statement const&) override; + void endVisit(Block const&) override; + void endVisit(IfStatement const&) override; + void endVisit(BreakableStatement const&) override; + void endVisit(WhileStatement const&) override; + void endVisit(ForStatement const&) override; + void endVisit(Continue const&) override; + void endVisit(Break const&) override; + void endVisit(Return const&) override; + void endVisit(VariableDefinition const&) override; + void endVisit(ExpressionStatement const&) override; + void endVisit(Expression const&) override; + void endVisit(Assignment const&) override; + void endVisit(UnaryOperation const&) override; + void endVisit(BinaryOperation const&) override; + void endVisit(FunctionCall const&) override; + void endVisit(NewExpression const&) override; + void endVisit(MemberAccess const&) override; + void endVisit(IndexAccess const&) override; + void endVisit(PrimaryExpression const&) override; + void endVisit(Identifier const&) override; + void endVisit(ElementaryTypeNameExpression const&) override; + void endVisit(Literal const&) override; + +private: + void addKeyValue(Json::Value& _obj, std::string const& _key, std::string const& _val); + void addJsonNode(std::string const& _nodeName, + std::initializer_list<std::pair<std::string const, std::string const>> _list, + bool _hasChildren); + std::string getType(Expression const& _expression); + inline void goUp() + { + solAssert(!m_jsonNodePtrs.empty(), "Uneven json nodes stack. Internal error."); + m_jsonNodePtrs.pop(); + }; + + Json::Value m_astJson; + std::stack<Json::Value*> m_jsonNodePtrs; + std::string m_source; + ASTNode const* m_ast; +}; + +} +} diff --git a/ASTPrinter.cpp b/ASTPrinter.cpp index 916fca1e..d380b002 100644 --- a/ASTPrinter.cpp +++ b/ASTPrinter.cpp @@ -57,6 +57,13 @@ bool ASTPrinter::visit(ContractDefinition const& _node) return goDeeper(); } +bool ASTPrinter::visit(InheritanceSpecifier const& _node) +{ + writeLine("InheritanceSpecifier \"" + _node.getName()->getName() + "\""); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(StructDefinition const& _node) { writeLine("StructDefinition \"" + _node.getName() + "\""); @@ -64,6 +71,18 @@ bool ASTPrinter::visit(StructDefinition const& _node) return goDeeper(); } +bool ASTPrinter::visit(EnumDefinition const& _node) +{ + writeLine("EnumDefinition \"" + _node.getName() + "\""); + return goDeeper(); +} + +bool ASTPrinter::visit(EnumValue const& _node) +{ + writeLine("EnumValue \"" + _node.getName() + "\""); + return goDeeper(); +} + bool ASTPrinter::visit(ParameterList const& _node) { writeLine("ParameterList"); @@ -87,6 +106,27 @@ bool ASTPrinter::visit(VariableDeclaration const& _node) return goDeeper(); } +bool ASTPrinter::visit(ModifierDefinition const& _node) +{ + writeLine("ModifierDefinition \"" + _node.getName() + "\""); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(ModifierInvocation const& _node) +{ + writeLine("ModifierInvocation \"" + _node.getName()->getName() + "\""); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(EventDefinition const& _node) +{ + writeLine("EventDefinition \"" + _node.getName() + "\""); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(TypeName const& _node) { writeLine("TypeName"); @@ -129,6 +169,13 @@ bool ASTPrinter::visit(Block const& _node) return goDeeper(); } +bool ASTPrinter::visit(PlaceholderStatement const& _node) +{ + writeLine("PlaceholderStatement"); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(IfStatement const& _node) { writeLine("IfStatement"); @@ -302,11 +349,26 @@ void ASTPrinter::endVisit(ContractDefinition const&) m_indentation--; } +void ASTPrinter::endVisit(InheritanceSpecifier const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(StructDefinition const&) { m_indentation--; } +void ASTPrinter::endVisit(EnumDefinition const&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(EnumValue const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(ParameterList const&) { m_indentation--; @@ -322,6 +384,21 @@ void ASTPrinter::endVisit(VariableDeclaration const&) m_indentation--; } +void ASTPrinter::endVisit(ModifierDefinition const&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(ModifierInvocation const&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(EventDefinition const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(TypeName const&) { m_indentation--; @@ -352,6 +429,11 @@ void ASTPrinter::endVisit(Block const&) m_indentation--; } +void ASTPrinter::endVisit(PlaceholderStatement const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(IfStatement const&) { m_indentation--; diff --git a/ASTPrinter.h b/ASTPrinter.h index fc5fb4ac..d9072aac 100644 --- a/ASTPrinter.h +++ b/ASTPrinter.h @@ -44,16 +44,23 @@ public: bool visit(ImportDirective const& _node) override; bool visit(ContractDefinition const& _node) override; + bool visit(InheritanceSpecifier const& _node) override; bool visit(StructDefinition const& _node) override; + bool visit(EnumDefinition const& _node) override; + bool visit(EnumValue const& _node) override; bool visit(ParameterList const& _node) override; bool visit(FunctionDefinition const& _node) override; bool visit(VariableDeclaration const& _node) override; + bool visit(ModifierDefinition const& _node) override; + bool visit(ModifierInvocation const& _node) override; + bool visit(EventDefinition const& _node) override; bool visit(TypeName const& _node) override; bool visit(ElementaryTypeName const& _node) override; bool visit(UserDefinedTypeName const& _node) override; bool visit(Mapping const& _node) override; bool visit(Statement const& _node) override; bool visit(Block const& _node) override; + bool visit(PlaceholderStatement const& _node) override; bool visit(IfStatement const& _node) override; bool visit(BreakableStatement const& _node) override; bool visit(WhileStatement const& _node) override; @@ -78,16 +85,23 @@ public: void endVisit(ImportDirective const&) override; void endVisit(ContractDefinition const&) override; + void endVisit(InheritanceSpecifier const&) override; void endVisit(StructDefinition const&) override; + void endVisit(EnumDefinition const&) override; + void endVisit(EnumValue const&) override; void endVisit(ParameterList const&) override; void endVisit(FunctionDefinition const&) override; void endVisit(VariableDeclaration const&) override; + void endVisit(ModifierDefinition const&) override; + void endVisit(ModifierInvocation const&) override; + void endVisit(EventDefinition const&) override; void endVisit(TypeName const&) override; void endVisit(ElementaryTypeName const&) override; void endVisit(UserDefinedTypeName const&) override; void endVisit(Mapping const&) override; void endVisit(Statement const&) override; void endVisit(Block const&) override; + void endVisit(PlaceholderStatement const&) override; void endVisit(IfStatement const&) override; void endVisit(BreakableStatement const&) override; void endVisit(WhileStatement const&) override; diff --git a/ASTVisitor.h b/ASTVisitor.h index 33a8a338..a7fa6b1c 100644 --- a/ASTVisitor.h +++ b/ASTVisitor.h @@ -45,16 +45,23 @@ public: virtual bool visit(SourceUnit&) { return true; } virtual bool visit(ImportDirective&) { return true; } virtual bool visit(ContractDefinition&) { return true; } + virtual bool visit(InheritanceSpecifier&) { return true; } virtual bool visit(StructDefinition&) { return true; } + virtual bool visit(EnumDefinition&) { return true; } + virtual bool visit(EnumValue&) { return true; } virtual bool visit(ParameterList&) { return true; } virtual bool visit(FunctionDefinition&) { return true; } virtual bool visit(VariableDeclaration&) { return true; } + virtual bool visit(ModifierDefinition&) { return true; } + virtual bool visit(ModifierInvocation&) { return true; } + virtual bool visit(EventDefinition&) { return true; } virtual bool visit(TypeName&) { return true; } virtual bool visit(ElementaryTypeName&) { return true; } virtual bool visit(UserDefinedTypeName&) { return true; } virtual bool visit(Mapping&) { return true; } virtual bool visit(Statement&) { return true; } virtual bool visit(Block&) { return true; } + virtual bool visit(PlaceholderStatement&) { return true; } virtual bool visit(IfStatement&) { return true; } virtual bool visit(BreakableStatement&) { return true; } virtual bool visit(WhileStatement&) { return true; } @@ -81,16 +88,23 @@ public: virtual void endVisit(SourceUnit&) { } virtual void endVisit(ImportDirective&) { } virtual void endVisit(ContractDefinition&) { } + virtual void endVisit(InheritanceSpecifier&) { } virtual void endVisit(StructDefinition&) { } + virtual void endVisit(EnumDefinition&) { } + virtual void endVisit(EnumValue&) { } virtual void endVisit(ParameterList&) { } virtual void endVisit(FunctionDefinition&) { } virtual void endVisit(VariableDeclaration&) { } + virtual void endVisit(ModifierDefinition&) { } + virtual void endVisit(ModifierInvocation&) { } + virtual void endVisit(EventDefinition&) { } virtual void endVisit(TypeName&) { } virtual void endVisit(ElementaryTypeName&) { } virtual void endVisit(UserDefinedTypeName&) { } virtual void endVisit(Mapping&) { } virtual void endVisit(Statement&) { } virtual void endVisit(Block&) { } + virtual void endVisit(PlaceholderStatement&) { } virtual void endVisit(IfStatement&) { } virtual void endVisit(BreakableStatement&) { } virtual void endVisit(WhileStatement&) { } @@ -121,16 +135,23 @@ public: virtual bool visit(SourceUnit const&) { return true; } virtual bool visit(ImportDirective const&) { return true; } virtual bool visit(ContractDefinition const&) { return true; } + virtual bool visit(InheritanceSpecifier const&) { return true; } virtual bool visit(StructDefinition const&) { return true; } + virtual bool visit(EnumDefinition const&) { return true; } + virtual bool visit(EnumValue const&) { return true; } virtual bool visit(ParameterList const&) { return true; } virtual bool visit(FunctionDefinition const&) { return true; } virtual bool visit(VariableDeclaration const&) { return true; } + virtual bool visit(ModifierDefinition const&) { return true; } + virtual bool visit(ModifierInvocation const&) { return true; } + virtual bool visit(EventDefinition const&) { return true; } virtual bool visit(TypeName const&) { return true; } virtual bool visit(ElementaryTypeName const&) { return true; } virtual bool visit(UserDefinedTypeName const&) { return true; } virtual bool visit(Mapping const&) { return true; } virtual bool visit(Statement const&) { return true; } virtual bool visit(Block const&) { return true; } + virtual bool visit(PlaceholderStatement const&) { return true; } virtual bool visit(IfStatement const&) { return true; } virtual bool visit(BreakableStatement const&) { return true; } virtual bool visit(WhileStatement const&) { return true; } @@ -157,16 +178,23 @@ public: virtual void endVisit(SourceUnit const&) { } virtual void endVisit(ImportDirective const&) { } virtual void endVisit(ContractDefinition const&) { } + virtual void endVisit(InheritanceSpecifier const&) { } virtual void endVisit(StructDefinition const&) { } + virtual void endVisit(EnumDefinition const&) { } + virtual void endVisit(EnumValue const&) { } virtual void endVisit(ParameterList const&) { } virtual void endVisit(FunctionDefinition const&) { } virtual void endVisit(VariableDeclaration const&) { } + virtual void endVisit(ModifierDefinition const&) { } + virtual void endVisit(ModifierInvocation const&) { } + virtual void endVisit(EventDefinition const&) { } virtual void endVisit(TypeName const&) { } virtual void endVisit(ElementaryTypeName const&) { } virtual void endVisit(UserDefinedTypeName const&) { } virtual void endVisit(Mapping const&) { } virtual void endVisit(Statement const&) { } virtual void endVisit(Block const&) { } + virtual void endVisit(PlaceholderStatement const&) { } virtual void endVisit(IfStatement const&) { } virtual void endVisit(BreakableStatement const&) { } virtual void endVisit(WhileStatement const&) { } diff --git a/AST_accept.h b/AST_accept.h index 0e5a71b6..b71e103d 100644 --- a/AST_accept.h +++ b/AST_accept.h @@ -61,8 +61,12 @@ void ContractDefinition::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) { + listAccept(m_baseContracts, _visitor); listAccept(m_definedStructs, _visitor); + listAccept(m_definedEnums, _visitor); listAccept(m_stateVariables, _visitor); + listAccept(m_events, _visitor); + listAccept(m_functionModifiers, _visitor); listAccept(m_definedFunctions, _visitor); } _visitor.endVisit(*this); @@ -72,13 +76,63 @@ void ContractDefinition::accept(ASTConstVisitor& _visitor) const { if (_visitor.visit(*this)) { + listAccept(m_baseContracts, _visitor); listAccept(m_definedStructs, _visitor); + listAccept(m_definedEnums, _visitor); listAccept(m_stateVariables, _visitor); + listAccept(m_events, _visitor); + listAccept(m_functionModifiers, _visitor); listAccept(m_definedFunctions, _visitor); } _visitor.endVisit(*this); } +void InheritanceSpecifier::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_baseName->accept(_visitor); + listAccept(m_arguments, _visitor); + } + _visitor.endVisit(*this); +} + +void InheritanceSpecifier::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_baseName->accept(_visitor); + listAccept(m_arguments, _visitor); + } + _visitor.endVisit(*this); +} + +void EnumDefinition::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + listAccept(m_members, _visitor); + _visitor.endVisit(*this); +} + +void EnumDefinition::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + listAccept(m_members, _visitor); + _visitor.endVisit(*this); +} + +void EnumValue::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void EnumValue::accept(ASTConstVisitor& _visitor) const +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + void StructDefinition::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) @@ -120,6 +174,7 @@ void FunctionDefinition::accept(ASTVisitor& _visitor) m_parameters->accept(_visitor); if (m_returnParameters) m_returnParameters->accept(_visitor); + listAccept(m_functionModifiers, _visitor); m_body->accept(_visitor); } _visitor.endVisit(*this); @@ -132,6 +187,7 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const m_parameters->accept(_visitor); if (m_returnParameters) m_returnParameters->accept(_visitor); + listAccept(m_functionModifiers, _visitor); m_body->accept(_visitor); } _visitor.endVisit(*this); @@ -153,6 +209,60 @@ void VariableDeclaration::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void ModifierDefinition::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_parameters->accept(_visitor); + m_body->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void ModifierDefinition::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_parameters->accept(_visitor); + m_body->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void ModifierInvocation::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_modifierName->accept(_visitor); + listAccept(m_arguments, _visitor); + } + _visitor.endVisit(*this); +} + +void ModifierInvocation::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_modifierName->accept(_visitor); + listAccept(m_arguments, _visitor); + } + _visitor.endVisit(*this); +} + +void EventDefinition::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + m_parameters->accept(_visitor); + _visitor.endVisit(*this); +} + +void EventDefinition::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + m_parameters->accept(_visitor); + _visitor.endVisit(*this); +} + void TypeName::accept(ASTVisitor& _visitor) { _visitor.visit(*this); @@ -223,6 +333,18 @@ void Block::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void PlaceholderStatement::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void PlaceholderStatement::accept(ASTConstVisitor& _visitor) const +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + void IfStatement::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) @@ -452,20 +574,14 @@ void FunctionCall::accept(ASTConstVisitor& _visitor) const void NewExpression::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) - { m_contractName->accept(_visitor); - listAccept(m_arguments, _visitor); - } _visitor.endVisit(*this); } void NewExpression::accept(ASTConstVisitor& _visitor) const { if (_visitor.visit(*this)) - { m_contractName->accept(_visitor); - listAccept(m_arguments, _visitor); - } _visitor.endVisit(*this); } diff --git a/BaseTypes.h b/BaseTypes.h index a8fd77c8..057289ef 100644 --- a/BaseTypes.h +++ b/BaseTypes.h @@ -41,6 +41,8 @@ struct Location start(_start), end(_end), sourceName(_sourceName) { } Location(): start(-1), end(-1) { } + bool isEmpty() const { return start == -1 && end == -1; } + int start; int end; std::shared_ptr<std::string const> sourceName; @@ -49,6 +51,8 @@ struct Location /// Stream output for Location (used e.g. in boost exceptions). inline std::ostream& operator<<(std::ostream& _out, Location const& _location) { + if (_location.isEmpty()) + return _out << "NO_LOCATION_SPECIFIED"; return _out << *_location.sourceName << "[" << _location.start << "," << _location.end << ")"; } diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a0b62bd..2b1059c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,9 +11,9 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB") aux_source_directory(. SRC_LIST) +include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS}) +include_directories(BEFORE ..) include_directories(${Boost_INCLUDE_DIRS}) -include_directories(${JSONCPP_INCLUDE_DIRS}) -include_directories(..) set(EXECUTABLE solidity) @@ -28,6 +28,7 @@ endif() target_link_libraries(${EXECUTABLE} ${JSONCPP_LIBRARIES}) target_link_libraries(${EXECUTABLE} evmcore) target_link_libraries(${EXECUTABLE} devcore) +target_link_libraries(${EXECUTABLE} devcrypto) install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) diff --git a/CallGraph.cpp b/CallGraph.cpp deleted file mode 100644 index b30afb61..00000000 --- a/CallGraph.cpp +++ /dev/null @@ -1,67 +0,0 @@ - -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. -*/ -/** - * @author Christian <c@ethdev.com> - * @date 2014 - * Callgraph of functions inside a contract. - */ - -#include <libsolidity/AST.h> -#include <libsolidity/CallGraph.h> - -using namespace std; - -namespace dev -{ -namespace solidity -{ - -void CallGraph::addFunction(FunctionDefinition const& _function) -{ - if (!m_functionsSeen.count(&_function)) - { - m_functionsSeen.insert(&_function); - m_workQueue.push(&_function); - } -} - -set<FunctionDefinition const*> const& CallGraph::getCalls() -{ - return m_functionsSeen; -} - -void CallGraph::computeCallGraph() -{ - while (!m_workQueue.empty()) - { - FunctionDefinition const* fun = m_workQueue.front(); - fun->accept(*this); - m_workQueue.pop(); - } -} - -bool CallGraph::visit(Identifier const& _identifier) -{ - FunctionDefinition const* fun = dynamic_cast<FunctionDefinition const*>(_identifier.getReferencedDeclaration()); - if (fun) - addFunction(*fun); - return true; -} - -} -} diff --git a/CallGraph.h b/CallGraph.h deleted file mode 100644 index f7af64bf..00000000 --- a/CallGraph.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. -*/ -/** - * @author Christian <c@ethdev.com> - * @date 2014 - * Callgraph of functions inside a contract. - */ - -#include <set> -#include <queue> -#include <boost/range/iterator_range.hpp> -#include <libsolidity/ASTVisitor.h> - -namespace dev -{ -namespace solidity -{ - -/** - * Can be used to compute the graph of calls (or rather references) between functions of the same - * contract. Current functionality is limited to computing all functions that are directly - * or indirectly called by some functions. - */ -class CallGraph: private ASTConstVisitor -{ -public: - void addFunction(FunctionDefinition const& _function); - void computeCallGraph(); - - std::set<FunctionDefinition const*> const& getCalls(); - -private: - void addFunctionToQueue(FunctionDefinition const& _function); - virtual bool visit(Identifier const& _identifier) override; - - std::set<FunctionDefinition const*> m_functionsSeen; - std::queue<FunctionDefinition const*> m_workQueue; -}; - -} -} diff --git a/Compiler.cpp b/Compiler.cpp index 394ae5f8..14acc011 100644 --- a/Compiler.cpp +++ b/Compiler.cpp @@ -21,72 +21,103 @@ */ #include <algorithm> +#include <boost/range/adaptor/reversed.hpp> #include <libevmcore/Instruction.h> #include <libevmcore/Assembly.h> #include <libsolidity/AST.h> #include <libsolidity/Compiler.h> #include <libsolidity/ExpressionCompiler.h> #include <libsolidity/CompilerUtils.h> -#include <libsolidity/CallGraph.h> using namespace std; namespace dev { namespace solidity { -void Compiler::compileContract(ContractDefinition const& _contract, vector<MagicVariableDeclaration const*> const& _magicGlobals, +void Compiler::compileContract(ContractDefinition const& _contract, map<ContractDefinition const*, bytes const*> const& _contracts) { m_context = CompilerContext(); // clear it just in case - initializeContext(_contract, _magicGlobals, _contracts); - - for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions()) - if (function->getName() != _contract.getName()) // don't add the constructor here - m_context.addFunction(*function); - + initializeContext(_contract, _contracts); appendFunctionSelector(_contract); - for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions()) - if (function->getName() != _contract.getName()) // don't add the constructor here + set<Declaration const*> functions = m_context.getFunctionsWithoutCode(); + while (!functions.empty()) + { + for (Declaration const* function: functions) function->accept(*this); + functions = m_context.getFunctionsWithoutCode(); + } // Swap the runtime context with the creation-time context - CompilerContext runtimeContext; - swap(m_context, runtimeContext); - initializeContext(_contract, _magicGlobals, _contracts); - packIntoContractCreator(_contract, runtimeContext); + swap(m_context, m_runtimeContext); + initializeContext(_contract, _contracts); + packIntoContractCreator(_contract, m_runtimeContext); } -void Compiler::initializeContext(ContractDefinition const& _contract, vector<MagicVariableDeclaration const*> const& _magicGlobals, +void Compiler::initializeContext(ContractDefinition const& _contract, map<ContractDefinition const*, bytes const*> const& _contracts) { m_context.setCompiledContracts(_contracts); - for (MagicVariableDeclaration const* variable: _magicGlobals) - m_context.addMagicGlobal(*variable); + m_context.setInheritanceHierarchy(_contract.getLinearizedBaseContracts()); registerStateVariables(_contract); } void Compiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext) { - set<FunctionDefinition const*> neededFunctions; - FunctionDefinition const* constructor = _contract.getConstructor(); - if (constructor) - neededFunctions = getFunctionsNeededByConstructor(*constructor); + // arguments for base constructors, filled in derived-to-base order + map<ContractDefinition const*, vector<ASTPointer<Expression>> const*> baseArguments; - for (FunctionDefinition const* fun: neededFunctions) - m_context.addFunction(*fun); + // Determine the arguments that are used for the base constructors. + std::vector<ContractDefinition const*> const& bases = _contract.getLinearizedBaseContracts(); + for (ContractDefinition const* contract: bases) + for (ASTPointer<InheritanceSpecifier> const& base: contract->getBaseContracts()) + { + ContractDefinition const* baseContract = dynamic_cast<ContractDefinition const*>( + base->getName()->getReferencedDeclaration()); + solAssert(baseContract, ""); + if (baseArguments.count(baseContract) == 0) + baseArguments[baseContract] = &base->getArguments(); + } - if (constructor) - appendConstructorCall(*constructor); + // Call constructors in base-to-derived order. + // The Constructor for the most derived contract is called later. + for (unsigned i = 1; i < bases.size(); i++) + { + ContractDefinition const* base = bases[bases.size() - i]; + solAssert(base, ""); + FunctionDefinition const* baseConstructor = base->getConstructor(); + if (!baseConstructor) + continue; + solAssert(baseArguments[base], ""); + appendBaseConstructorCall(*baseConstructor, *baseArguments[base]); + } + if (_contract.getConstructor()) + appendConstructorCall(*_contract.getConstructor()); eth::AssemblyItem sub = m_context.addSubroutine(_runtimeContext.getAssembly()); // stack contains sub size m_context << eth::Instruction::DUP1 << sub << u256(0) << eth::Instruction::CODECOPY; m_context << u256(0) << eth::Instruction::RETURN; - // note that we have to explicitly include all used functions because of absolute jump - // labels - for (FunctionDefinition const* fun: neededFunctions) - fun->accept(*this); + // note that we have to include the functions again because of absolute jump labels + set<Declaration const*> functions = m_context.getFunctionsWithoutCode(); + while (!functions.empty()) + { + for (Declaration const* function: functions) + function->accept(*this); + functions = m_context.getFunctionsWithoutCode(); + } +} + +void Compiler::appendBaseConstructorCall(FunctionDefinition const& _constructor, + vector<ASTPointer<Expression>> const& _arguments) +{ + FunctionType constructorType(_constructor); + eth::AssemblyItem returnLabel = m_context.pushNewTag(); + for (unsigned i = 0; i < _arguments.size(); ++i) + compileExpression(*_arguments[i], constructorType.getParameterTypes()[i]); + m_context.appendJumpTo(m_context.getFunctionEntryLabel(_constructor)); + m_context << returnLabel; } void Compiler::appendConstructorCall(FunctionDefinition const& _constructor) @@ -95,104 +126,115 @@ void Compiler::appendConstructorCall(FunctionDefinition const& _constructor) // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program unsigned argumentSize = 0; for (ASTPointer<VariableDeclaration> const& var: _constructor.getParameters()) - argumentSize += var->getType()->getCalldataEncodedSize(); + argumentSize += CompilerUtils::getPaddedSize(var->getType()->getCalldataEncodedSize()); + if (argumentSize > 0) { m_context << u256(argumentSize); m_context.appendProgramSize(); - m_context << u256(1); // copy it to byte one as expected for ABI calls + m_context << u256(CompilerUtils::dataStartOffset); // copy it to byte four as expected for ABI calls m_context << eth::Instruction::CODECOPY; - appendCalldataUnpacker(_constructor, true); + appendCalldataUnpacker(FunctionType(_constructor).getParameterTypes(), true); } m_context.appendJumpTo(m_context.getFunctionEntryLabel(_constructor)); m_context << returnTag; } -set<FunctionDefinition const*> Compiler::getFunctionsNeededByConstructor(FunctionDefinition const& _constructor) -{ - CallGraph callgraph; - callgraph.addFunction(_constructor); - callgraph.computeCallGraph(); - return callgraph.getCalls(); -} - void Compiler::appendFunctionSelector(ContractDefinition const& _contract) { - vector<FunctionDefinition const*> interfaceFunctions = _contract.getInterfaceFunctions(); - vector<eth::AssemblyItem> callDataUnpackerEntryPoints; - - if (interfaceFunctions.size() > 255) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("More than 255 public functions for contract.")); - - // retrieve the first byte of the call data, which determines the called function - // @todo This code had a jump table in a previous version which was more efficient but also - // error prone (due to the optimizer and variable length tag addresses) - m_context << u256(1) << u256(0) // some constants - << eth::dupInstruction(1) << eth::Instruction::CALLDATALOAD - << eth::dupInstruction(2) << eth::Instruction::BYTE - << eth::dupInstruction(2); - - // stack here: 1 0 <funid> 0, stack top will be counted up until it matches funid - for (unsigned funid = 0; funid < interfaceFunctions.size(); ++funid) + map<FixedHash<4>, FunctionTypePointer> interfaceFunctions = _contract.getInterfaceFunctions(); + map<FixedHash<4>, const eth::AssemblyItem> callDataUnpackerEntryPoints; + + // retrieve the function signature hash from the calldata + if (!interfaceFunctions.empty()) + CompilerUtils(m_context).loadFromMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8), true); + + // stack now is: 1 0 <funhash> + for (auto const& it: interfaceFunctions) { - callDataUnpackerEntryPoints.push_back(m_context.newTag()); - m_context << eth::dupInstruction(2) << eth::dupInstruction(2) << eth::Instruction::EQ; - m_context.appendConditionalJumpTo(callDataUnpackerEntryPoints.back()); - if (funid < interfaceFunctions.size() - 1) - m_context << eth::dupInstruction(4) << eth::Instruction::ADD; + callDataUnpackerEntryPoints.insert(std::make_pair(it.first, m_context.newTag())); + m_context << eth::dupInstruction(1) << u256(FixedHash<4>::Arith(it.first)) << eth::Instruction::EQ; + m_context.appendConditionalJumpTo(callDataUnpackerEntryPoints.at(it.first)); } - m_context << eth::Instruction::STOP; // function not found + if (FunctionDefinition const* fallback = _contract.getFallbackFunction()) + { + eth::AssemblyItem returnTag = m_context.pushNewTag(); + fallback->accept(*this); + m_context << returnTag; + appendReturnValuePacker(FunctionType(*fallback).getReturnParameterTypes()); + } + else + m_context << eth::Instruction::STOP; // function not found - for (unsigned funid = 0; funid < interfaceFunctions.size(); ++funid) + for (auto const& it: interfaceFunctions) { - FunctionDefinition const& function = *interfaceFunctions[funid]; - m_context << callDataUnpackerEntryPoints[funid]; + FunctionTypePointer const& functionType = it.second; + m_context << callDataUnpackerEntryPoints.at(it.first); eth::AssemblyItem returnTag = m_context.pushNewTag(); - appendCalldataUnpacker(function); - m_context.appendJumpTo(m_context.getFunctionEntryLabel(function)); + appendCalldataUnpacker(functionType->getParameterTypes()); + m_context.appendJumpTo(m_context.getFunctionEntryLabel(it.second->getDeclaration())); m_context << returnTag; - appendReturnValuePacker(function); + appendReturnValuePacker(functionType->getReturnParameterTypes()); } } -unsigned Compiler::appendCalldataUnpacker(FunctionDefinition const& _function, bool _fromMemory) +void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory) { // We do not check the calldata size, everything is zero-padded. - unsigned dataOffset = 1; - //@todo this can be done more efficiently, saving some CALLDATALOAD calls - for (ASTPointer<VariableDeclaration> const& var: _function.getParameters()) - { - unsigned const numBytes = var->getType()->getCalldataEncodedSize(); - if (numBytes > 32) - BOOST_THROW_EXCEPTION(CompilerError() - << errinfo_sourceLocation(var->getLocation()) - << errinfo_comment("Type " + var->getType()->toString() + " not yet supported.")); - bool leftAligned = var->getType()->getCategory() == Type::Category::STRING; - CompilerUtils(m_context).loadFromMemory(dataOffset, numBytes, leftAligned, !_fromMemory); - dataOffset += numBytes; - } - return dataOffset; + unsigned offset(CompilerUtils::dataStartOffset); + bool const c_padToWords = true; + + unsigned dynamicParameterCount = 0; + for (TypePointer const& type: _typeParameters) + if (type->isDynamicallySized()) + dynamicParameterCount++; + offset += dynamicParameterCount * 32; + unsigned currentDynamicParameter = 0; + for (TypePointer const& type: _typeParameters) + if (type->isDynamicallySized()) + { + // value on stack: [calldata_offset] (only if we are already in dynamic mode) + if (currentDynamicParameter == 0) + // switch from static to dynamic + m_context << u256(offset); + // retrieve length + CompilerUtils(m_context).loadFromMemory( + CompilerUtils::dataStartOffset + currentDynamicParameter * 32, + IntegerType(256), !_fromMemory, c_padToWords); + // stack: offset length + // add 32-byte padding to copy of length + m_context << u256(32) << eth::Instruction::DUP1 << u256(31) + << eth::Instruction::DUP4 << eth::Instruction::ADD + << eth::Instruction::DIV << eth::Instruction::MUL; + // stack: offset length padded_length + m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; + currentDynamicParameter++; + // stack: offset length next_calldata_offset + } + else if (currentDynamicParameter == 0) + // we can still use static load + offset += CompilerUtils(m_context).loadFromMemory(offset, *type, !_fromMemory, c_padToWords); + else + CompilerUtils(m_context).loadFromMemoryDynamic(*type, !_fromMemory, c_padToWords); + if (dynamicParameterCount > 0) + m_context << eth::Instruction::POP; } -void Compiler::appendReturnValuePacker(FunctionDefinition const& _function) +void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters) { //@todo this can be also done more efficiently unsigned dataOffset = 0; - vector<ASTPointer<VariableDeclaration>> const& parameters = _function.getReturnParameters(); - unsigned stackDepth = CompilerUtils(m_context).getSizeOnStack(parameters); - for (unsigned i = 0; i < parameters.size(); ++i) + unsigned stackDepth = 0; + for (TypePointer const& type: _typeParameters) + stackDepth += type->getSizeOnStack(); + + for (TypePointer const& type: _typeParameters) { - Type const& paramType = *parameters[i]->getType(); - unsigned numBytes = paramType.getCalldataEncodedSize(); - if (numBytes > 32) - BOOST_THROW_EXCEPTION(CompilerError() - << errinfo_sourceLocation(parameters[i]->getLocation()) - << errinfo_comment("Type " + paramType.toString() + " not yet supported.")); - CompilerUtils(m_context).copyToStackTop(stackDepth, paramType); - bool const leftAligned = paramType.getCategory() == Type::Category::STRING; - CompilerUtils(m_context).storeInMemory(dataOffset, numBytes, leftAligned); - stackDepth -= paramType.getSizeOnStack(); - dataOffset += numBytes; + CompilerUtils(m_context).copyToStackTop(stackDepth, *type); + ExpressionCompiler::appendTypeConversion(m_context, *type, *type, true); + bool const c_padToWords = true; + dataOffset += CompilerUtils(m_context).storeInMemory(dataOffset, *type, c_padToWords); + stackDepth -= type->getSizeOnStack(); } // note that the stack is not cleaned up here m_context << u256(dataOffset) << u256(0) << eth::Instruction::RETURN; @@ -200,9 +242,23 @@ void Compiler::appendReturnValuePacker(FunctionDefinition const& _function) void Compiler::registerStateVariables(ContractDefinition const& _contract) { - //@todo sort them? - for (ASTPointer<VariableDeclaration> const& variable: _contract.getStateVariables()) - m_context.addStateVariable(*variable); + for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.getLinearizedBaseContracts())) + for (ASTPointer<VariableDeclaration> const& variable: contract->getStateVariables()) + m_context.addStateVariable(*variable); +} + +bool Compiler::visit(VariableDeclaration const& _variableDeclaration) +{ + solAssert(_variableDeclaration.isStateVariable(), "Compiler visit to non-state variable declaration."); + + m_context.startFunction(_variableDeclaration); + m_breakTags.clear(); + m_continueTags.clear(); + + m_context << m_context.getFunctionEntryLabel(_variableDeclaration); + ExpressionCompiler::appendStateVariableAccessor(m_context, _variableDeclaration); + + return false; } bool Compiler::visit(FunctionDefinition const& _function) @@ -211,24 +267,30 @@ bool Compiler::visit(FunctionDefinition const& _function) // caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn] // although note that this reduces the size of the visible stack - m_context.startNewFunction(); + m_context.startFunction(_function); m_returnTag = m_context.newTag(); m_breakTags.clear(); m_continueTags.clear(); - - m_context << m_context.getFunctionEntryLabel(_function); + m_stackCleanupForReturn = 0; + m_currentFunction = &_function; + m_modifierDepth = 0; // stack upon entry: [return address] [arg0] [arg1] ... [argn] // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp] + unsigned parametersSize = CompilerUtils::getSizeOnStack(_function.getParameters()); + m_context.adjustStackOffset(parametersSize); for (ASTPointer<VariableDeclaration const> const& variable: _function.getParameters()) - m_context.addVariable(*variable); + { + m_context.addVariable(*variable, parametersSize); + parametersSize -= variable->getType()->getSizeOnStack(); + } for (ASTPointer<VariableDeclaration const> const& variable: _function.getReturnParameters()) m_context.addAndInitializeVariable(*variable); for (VariableDeclaration const* localVariable: _function.getLocalVariables()) m_context.addAndInitializeVariable(*localVariable); - _function.getBody().accept(*this); + appendModifierOrFunctionCode(); m_context << m_returnTag; @@ -238,16 +300,16 @@ bool Compiler::visit(FunctionDefinition const& _function) // Note that the fact that the return arguments are of increasing index is vital for this // algorithm to work. - unsigned const argumentsSize = CompilerUtils::getSizeOnStack(_function.getParameters()); - unsigned const returnValuesSize = CompilerUtils::getSizeOnStack(_function.getReturnParameters()); - unsigned const localVariablesSize = CompilerUtils::getSizeOnStack(_function.getLocalVariables()); + unsigned const c_argumentsSize = CompilerUtils::getSizeOnStack(_function.getParameters()); + unsigned const c_returnValuesSize = CompilerUtils::getSizeOnStack(_function.getReturnParameters()); + unsigned const c_localVariablesSize = CompilerUtils::getSizeOnStack(_function.getLocalVariables()); vector<int> stackLayout; - stackLayout.push_back(returnValuesSize); // target of return address - stackLayout += vector<int>(argumentsSize, -1); // discard all arguments - for (unsigned i = 0; i < returnValuesSize; ++i) + stackLayout.push_back(c_returnValuesSize); // target of return address + stackLayout += vector<int>(c_argumentsSize, -1); // discard all arguments + for (unsigned i = 0; i < c_returnValuesSize; ++i) stackLayout.push_back(i); - stackLayout += vector<int>(localVariablesSize, -1); + stackLayout += vector<int>(c_localVariablesSize, -1); while (stackLayout.back() != int(stackLayout.size() - 1)) if (stackLayout.back() < 0) @@ -355,13 +417,15 @@ bool Compiler::visit(Return const& _return) //@todo modifications are needed to make this work with functions returning multiple values if (Expression const* expression = _return.getExpression()) { - compileExpression(*expression); - VariableDeclaration const& firstVariable = *_return.getFunctionReturnParameters().getParameters().front(); - ExpressionCompiler::appendTypeConversion(m_context, *expression->getType(), *firstVariable.getType()); - + solAssert(_return.getFunctionReturnParameters(), "Invalid return parameters pointer."); + VariableDeclaration const& firstVariable = *_return.getFunctionReturnParameters()->getParameters().front(); + compileExpression(*expression, firstVariable.getType()); CompilerUtils(m_context).moveToStackVariable(firstVariable); } + for (unsigned i = 0; i < m_stackCleanupForReturn; ++i) + m_context << eth::Instruction::POP; m_context.appendJumpTo(m_returnTag); + m_context.adjustStackOffset(m_stackCleanupForReturn); return false; } @@ -369,10 +433,7 @@ bool Compiler::visit(VariableDefinition const& _variableDefinition) { if (Expression const* expression = _variableDefinition.getExpression()) { - compileExpression(*expression); - ExpressionCompiler::appendTypeConversion(m_context, - *expression->getType(), - *_variableDefinition.getDeclaration().getType()); + compileExpression(*expression, _variableDefinition.getDeclaration().getType()); CompilerUtils(m_context).moveToStackVariable(_variableDefinition.getDeclaration()); } return false; @@ -386,9 +447,51 @@ bool Compiler::visit(ExpressionStatement const& _expressionStatement) return false; } -void Compiler::compileExpression(Expression const& _expression) +bool Compiler::visit(PlaceholderStatement const&) +{ + ++m_modifierDepth; + appendModifierOrFunctionCode(); + --m_modifierDepth; + return true; +} + +void Compiler::appendModifierOrFunctionCode() +{ + solAssert(m_currentFunction, ""); + if (m_modifierDepth >= m_currentFunction->getModifiers().size()) + m_currentFunction->getBody().accept(*this); + else + { + ASTPointer<ModifierInvocation> const& modifierInvocation = m_currentFunction->getModifiers()[m_modifierDepth]; + + ModifierDefinition const& modifier = m_context.getFunctionModifier(modifierInvocation->getName()->getName()); + solAssert(modifier.getParameters().size() == modifierInvocation->getArguments().size(), ""); + for (unsigned i = 0; i < modifier.getParameters().size(); ++i) + { + m_context.addVariable(*modifier.getParameters()[i]); + compileExpression(*modifierInvocation->getArguments()[i], + modifier.getParameters()[i]->getType()); + } + for (VariableDeclaration const* localVariable: modifier.getLocalVariables()) + m_context.addAndInitializeVariable(*localVariable); + + unsigned const c_stackSurplus = CompilerUtils::getSizeOnStack(modifier.getParameters()) + + CompilerUtils::getSizeOnStack(modifier.getLocalVariables()); + m_stackCleanupForReturn += c_stackSurplus; + + modifier.getBody().accept(*this); + + for (unsigned i = 0; i < c_stackSurplus; ++i) + m_context << eth::Instruction::POP; + m_stackCleanupForReturn -= c_stackSurplus; + } +} + +void Compiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) { ExpressionCompiler::compileExpression(m_context, _expression, m_optimize); + if (_targetType) + ExpressionCompiler::appendTypeConversion(m_context, *_expression.getType(), *_targetType); } } @@ -20,7 +20,10 @@ * Solidity AST to EVM bytecode compiler. */ +#pragma once + #include <ostream> +#include <functional> #include <libsolidity/ASTVisitor.h> #include <libsolidity/CompilerContext.h> @@ -30,32 +33,34 @@ namespace solidity { class Compiler: private ASTConstVisitor { public: - explicit Compiler(bool _optimize = false): m_optimize(_optimize), m_returnTag(m_context.newTag()) {} + explicit Compiler(bool _optimize = false): m_optimize(_optimize), m_context(), + m_returnTag(m_context.newTag()) {} - void compileContract(ContractDefinition const& _contract, std::vector<MagicVariableDeclaration const*> const& _magicGlobals, + void compileContract(ContractDefinition const& _contract, std::map<ContractDefinition const*, bytes const*> const& _contracts); bytes getAssembledBytecode() { return m_context.getAssembledBytecode(m_optimize); } + bytes getRuntimeBytecode() { return m_runtimeContext.getAssembledBytecode(m_optimize);} void streamAssembly(std::ostream& _stream) const { m_context.streamAssembly(_stream); } private: - /// Registers the global objects and the non-function objects inside the contract with the context. - void initializeContext(ContractDefinition const& _contract, std::vector<MagicVariableDeclaration const*> const& _magicGlobals, + /// Registers the non-function objects inside the contract with the context. + void initializeContext(ContractDefinition const& _contract, std::map<ContractDefinition const*, bytes const*> const& _contracts); /// Adds the code that is run at creation time. Should be run after exchanging the run-time context - /// with a new and initialized context. - /// adds the constructor code. + /// with a new and initialized context. Adds the constructor code. void packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext); + void appendBaseConstructorCall(FunctionDefinition const& _constructor, + std::vector<ASTPointer<Expression>> const& _arguments); void appendConstructorCall(FunctionDefinition const& _constructor); - /// Recursively searches the call graph and returns all functions needed by the constructor (including itself). - std::set<FunctionDefinition const*> getFunctionsNeededByConstructor(FunctionDefinition const& _constructor); void appendFunctionSelector(ContractDefinition const& _contract); - /// Creates code that unpacks the arguments for the given function, from memory if - /// @a _fromMemory is true, otherwise from call data. @returns the size of the data in bytes. - unsigned appendCalldataUnpacker(FunctionDefinition const& _function, bool _fromMemory = false); - void appendReturnValuePacker(FunctionDefinition const& _function); + /// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers. + /// From memory if @a _fromMemory is true, otherwise from call data. + void appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory = false); + void appendReturnValuePacker(TypePointers const& _typeParameters); void registerStateVariables(ContractDefinition const& _contract); + virtual bool visit(VariableDeclaration const& _variableDeclaration) override; virtual bool visit(FunctionDefinition const& _function) override; virtual bool visit(IfStatement const& _ifStatement) override; virtual bool visit(WhileStatement const& _whileStatement) override; @@ -65,14 +70,23 @@ private: virtual bool visit(Return const& _return) override; virtual bool visit(VariableDefinition const& _variableDefinition) override; virtual bool visit(ExpressionStatement const& _expressionStatement) override; + virtual bool visit(PlaceholderStatement const&) override; + + /// Appends one layer of function modifier code of the current function, or the function + /// body itself if the last modifier was reached. + void appendModifierOrFunctionCode(); - void compileExpression(Expression const& _expression); + void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); bool const m_optimize; CompilerContext m_context; + CompilerContext m_runtimeContext; std::vector<eth::AssemblyItem> m_breakTags; ///< tag to jump to for a "break" statement std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement eth::AssemblyItem m_returnTag; ///< tag to jump to for a "return" statement + unsigned m_modifierDepth = 0; + FunctionDefinition const* m_currentFunction; + unsigned m_stackCleanupForReturn; ///< this number of stack elements need to be removed before jump to m_returnTag }; } diff --git a/CompilerContext.cpp b/CompilerContext.cpp index 18357bf0..01a71d7c 100644 --- a/CompilerContext.cpp +++ b/CompilerContext.cpp @@ -43,25 +43,28 @@ void CompilerContext::addStateVariable(VariableDeclaration const& _declaration) m_stateVariablesSize += _declaration.getType()->getStorageSize(); } -void CompilerContext::addVariable(VariableDeclaration const& _declaration) +void CompilerContext::startFunction(Declaration const& _function) { - m_localVariables[&_declaration] = m_localVariablesSize; - m_localVariablesSize += _declaration.getType()->getSizeOnStack(); + m_functionsWithCode.insert(&_function); + m_localVariables.clear(); + m_asm.setDeposit(0); + *this << getFunctionEntryLabel(_function); +} + +void CompilerContext::addVariable(VariableDeclaration const& _declaration, + unsigned _offsetToCurrent) +{ + solAssert(m_asm.deposit() >= 0 && unsigned(m_asm.deposit()) >= _offsetToCurrent, ""); + m_localVariables[&_declaration] = unsigned(m_asm.deposit()) - _offsetToCurrent; } void CompilerContext::addAndInitializeVariable(VariableDeclaration const& _declaration) { addVariable(_declaration); - unsigned const size = _declaration.getType()->getSizeOnStack(); - for (unsigned i = 0; i < size; ++i) + int const size = _declaration.getType()->getSizeOnStack(); + for (int i = 0; i < size; ++i) *this << u256(0); - m_asm.adjustDeposit(-size); -} - -void CompilerContext::addFunction(FunctionDefinition const& _function) -{ - m_functionEntryLabels.insert(std::make_pair(&_function, m_asm.newTag())); } bytes const& CompilerContext::getCompiledContract(const ContractDefinition& _contract) const @@ -73,26 +76,82 @@ bytes const& CompilerContext::getCompiledContract(const ContractDefinition& _con bool CompilerContext::isLocalVariable(Declaration const* _declaration) const { - return m_localVariables.count(_declaration) > 0; + return !!m_localVariables.count(_declaration); } -eth::AssemblyItem CompilerContext::getFunctionEntryLabel(FunctionDefinition const& _function) const +eth::AssemblyItem CompilerContext::getFunctionEntryLabel(Declaration const& _declaration) { - auto res = m_functionEntryLabels.find(&_function); - solAssert(res != m_functionEntryLabels.end(), "Function entry label not found."); - return res->second.tag(); + auto res = m_functionEntryLabels.find(&_declaration); + if (res == m_functionEntryLabels.end()) + { + eth::AssemblyItem tag(m_asm.newTag()); + m_functionEntryLabels.insert(make_pair(&_declaration, tag)); + return tag.tag(); + } + else + return res->second.tag(); +} + +eth::AssemblyItem CompilerContext::getVirtualFunctionEntryLabel(FunctionDefinition const& _function) +{ + solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); + for (ContractDefinition const* contract: m_inheritanceHierarchy) + for (ASTPointer<FunctionDefinition> const& function: contract->getDefinedFunctions()) + if (!function->isConstructor() && function->getName() == _function.getName()) + return getFunctionEntryLabel(*function); + solAssert(false, "Virtual function " + _function.getName() + " not found."); + return m_asm.newTag(); // not reached +} + +eth::AssemblyItem CompilerContext::getSuperFunctionEntryLabel(string const& _name, ContractDefinition const& _base) +{ + // search for first contract after _base + solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); + auto it = find(m_inheritanceHierarchy.begin(), m_inheritanceHierarchy.end(), &_base); + solAssert(it != m_inheritanceHierarchy.end(), "Base not found in inheritance hierarchy."); + for (++it; it != m_inheritanceHierarchy.end(); ++it) + for (ASTPointer<FunctionDefinition> const& function: (*it)->getDefinedFunctions()) + if (!function->isConstructor() && function->getName() == _name) + return getFunctionEntryLabel(*function); + solAssert(false, "Super function " + _name + " not found."); + return m_asm.newTag(); // not reached +} + +set<Declaration const*> CompilerContext::getFunctionsWithoutCode() +{ + set<Declaration const*> functions; + for (auto const& it: m_functionEntryLabels) + if (m_functionsWithCode.count(it.first) == 0) + functions.insert(it.first); + return move(functions); +} + +ModifierDefinition const& CompilerContext::getFunctionModifier(string const& _name) const +{ + solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); + for (ContractDefinition const* contract: m_inheritanceHierarchy) + for (ASTPointer<ModifierDefinition> const& modifier: contract->getFunctionModifiers()) + if (modifier->getName() == _name) + return *modifier.get(); + BOOST_THROW_EXCEPTION(InternalCompilerError() + << errinfo_comment("Function modifier " + _name + " not found.")); } unsigned CompilerContext::getBaseStackOffsetOfVariable(Declaration const& _declaration) const { auto res = m_localVariables.find(&_declaration); solAssert(res != m_localVariables.end(), "Variable not found on stack."); - return m_localVariablesSize - res->second - 1; + return res->second; } unsigned CompilerContext::baseToCurrentStackOffset(unsigned _baseOffset) const { - return _baseOffset + m_asm.deposit(); + return m_asm.deposit() - _baseOffset - 1; +} + +unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const +{ + return m_asm.deposit() - _offset - 1; } u256 CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const diff --git a/CompilerContext.h b/CompilerContext.h index 795f447a..f202d7f4 100644 --- a/CompilerContext.h +++ b/CompilerContext.h @@ -41,27 +41,41 @@ class CompilerContext public: void addMagicGlobal(MagicVariableDeclaration const& _declaration); void addStateVariable(VariableDeclaration const& _declaration); - void startNewFunction() { m_localVariables.clear(); m_asm.setDeposit(0); } - void addVariable(VariableDeclaration const& _declaration); + void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); void addAndInitializeVariable(VariableDeclaration const& _declaration); - void addFunction(FunctionDefinition const& _function); void setCompiledContracts(std::map<ContractDefinition const*, bytes const*> const& _contracts) { m_compiledContracts = _contracts; } bytes const& getCompiledContract(ContractDefinition const& _contract) const; void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); } + unsigned getStackHeight() { solAssert(m_asm.deposit() >= 0, ""); return unsigned(m_asm.deposit()); } - bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration); } - bool isFunctionDefinition(Declaration const* _declaration) const { return m_functionEntryLabels.count(_declaration); } + bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration) != 0; } bool isLocalVariable(Declaration const* _declaration) const; - bool isStateVariable(Declaration const* _declaration) const { return m_stateVariables.count(_declaration); } - - eth::AssemblyItem getFunctionEntryLabel(FunctionDefinition const& _function) const; - /// Returns the distance of the given local variable from the top of the local variable stack. + bool isStateVariable(Declaration const* _declaration) const { return m_stateVariables.count(_declaration) != 0; } + + eth::AssemblyItem getFunctionEntryLabel(Declaration const& _declaration); + void setInheritanceHierarchy(std::vector<ContractDefinition const*> const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; } + /// @returns the entry label of the given function and takes overrides into account. + eth::AssemblyItem getVirtualFunctionEntryLabel(FunctionDefinition const& _function); + /// @returns the entry label of function with the given name from the most derived class just + /// above _base in the current inheritance hierarchy. + eth::AssemblyItem getSuperFunctionEntryLabel(std::string const& _name, ContractDefinition const& _base); + /// @returns the set of functions for which we still need to generate code + std::set<Declaration const*> getFunctionsWithoutCode(); + /// Resets function specific members, inserts the function entry label and marks the function + /// as "having code". + void startFunction(Declaration const& _function); + + ModifierDefinition const& getFunctionModifier(std::string const& _name) const; + /// Returns the distance of the given local variable from the bottom of the stack (of the current function). unsigned getBaseStackOffsetOfVariable(Declaration const& _declaration) const; /// If supplied by a value returned by @ref getBaseStackOffsetOfVariable(variable), returns /// the distance of that variable from the current top of the stack. unsigned baseToCurrentStackOffset(unsigned _baseOffset) const; + /// Converts an offset relative to the current stack height to a value that can be used later + /// with baseToCurrentStackOffset to point to the same stack element. + unsigned currentToBaseStackOffset(unsigned _offset) const; u256 getStorageLocationOfVariable(Declaration const& _declaration) const; /// Appends a JUMPI instruction to a new tag and @returns the tag @@ -109,10 +123,12 @@ private: std::map<Declaration const*, u256> m_stateVariables; /// Offsets of local variables on the stack (relative to stack base). std::map<Declaration const*, unsigned> m_localVariables; - /// Sum of stack sizes of local variables - unsigned m_localVariablesSize; - /// Labels pointing to the entry points of funcitons. + /// Labels pointing to the entry points of functions. std::map<Declaration const*, eth::AssemblyItem> m_functionEntryLabels; + /// Set of functions for which we did not yet generate code. + std::set<Declaration const*> m_functionsWithCode; + /// List of current inheritance hierarchy from derived to base. + std::vector<ContractDefinition const*> m_inheritanceHierarchy; }; } diff --git a/CompilerStack.cpp b/CompilerStack.cpp index 79716fde..ca9c75bc 100644 --- a/CompilerStack.cpp +++ b/CompilerStack.cpp @@ -16,10 +16,12 @@ */ /** * @author Christian <c@ethdev.com> + * @author Gav Wood <g@ethdev.com> * @date 2014 * Full-stack compiler that converts a source code string to bytecode. */ +#include <boost/algorithm/string.hpp> #include <libsolidity/AST.h> #include <libsolidity/Scanner.h> #include <libsolidity/Parser.h> @@ -29,6 +31,8 @@ #include <libsolidity/CompilerStack.h> #include <libsolidity/InterfaceHandler.h> +#include <libdevcrypto/SHA3.h> + using namespace std; namespace dev @@ -36,18 +40,39 @@ namespace dev namespace solidity { +const map<string, string> StandardSources = map<string, string>{ + {"coin", R"(import "CoinReg";import "Config";import "configUser";contract coin is configUser{function coin(string3 name, uint denom) {CoinReg(Config(configAddr()).lookup(3)).register(name, denom);}})"}, + {"Coin", R"(contract Coin{function isApprovedFor(address _target,address _proxy)constant returns(bool _r){}function isApproved(address _proxy)constant returns(bool _r){}function sendCoinFrom(address _from,uint256 _val,address _to){}function coinBalanceOf(address _a)constant returns(uint256 _r){}function sendCoin(uint256 _val,address _to){}function coinBalance()constant returns(uint256 _r){}function approve(address _a){}})"}, + {"CoinReg", R"(contract CoinReg{function count()constant returns(uint256 r){}function info(uint256 i)constant returns(address addr,string3 name,uint256 denom){}function register(string3 name,uint256 denom){}function unregister(){}})"}, + {"configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xc6d9d2cd449a754c494264e1809c50e34d64562b;}})"}, + {"Config", R"(contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}})"}, + {"mortal", R"(import "owned";contract mortal is owned {function kill() { if (msg.sender == owner) suicide(owner); }})"}, + {"named", R"(import "Config";import "NameReg";import "configUser";contract named is configUser {function named(string32 name) {NameReg(Config(configAddr()).lookup(1)).register(name);}})"}, + {"NameReg", R"(contract NameReg{function register(string32 name){}function addressOf(string32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(string32 name){}})"}, + {"owned", R"(contract owned{function owned(){owner = msg.sender;}modifier onlyowner(){if(msg.sender==owner)_}address owner;})"}, + {"service", R"(import "Config";import "configUser";contract service is configUser{function service(uint _n){Config(configAddr()).register(_n, this);}})"}, + {"std", R"(import "owned";import "mortal";import "Config";import "configUser";import "NameReg";import "named";)"} +}; + +CompilerStack::CompilerStack(bool _addStandardSources): + m_addStandardSources(_addStandardSources), m_parseSuccessful(false) +{ + if (m_addStandardSources) + addSources(StandardSources); +} + bool CompilerStack::addSource(string const& _name, string const& _content) { - bool existed = m_sources.count(_name); + bool existed = m_sources.count(_name) != 0; reset(true); - m_sources[_name].scanner = make_shared<Scanner>(CharStream(_content), _name); + m_sources[_name].scanner = make_shared<Scanner>(CharStream(expanded(_content)), _name); return existed; } void CompilerStack::setSource(string const& _sourceCode) { reset(); - addSource("", _sourceCode); + addSource("", expanded(_sourceCode)); } void CompilerStack::parse() @@ -69,6 +94,7 @@ void CompilerStack::parse() { m_globalContext->setCurrentContract(*contract); resolver.updateDeclaration(*m_globalContext->getCurrentThis()); + resolver.updateDeclaration(*m_globalContext->getCurrentSuper()); resolver.resolveNamesAndTypes(*contract); m_contracts[contract->getName()].contract = contract; } @@ -100,6 +126,58 @@ vector<string> CompilerStack::getContractNames() const return contractNames; } +////// BEGIN: TEMPORARY ONLY +/// +/// NOTE: THIS INVALIDATES SOURCE POINTERS AND CAN CRASH THE COMPILER +/// +/// remove once import works properly and we have genesis contracts + +string CompilerStack::expanded(string const& _sourceCode) +{ + const map<string, string> c_standardSources = map<string, string>{ + { "Config", "contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}}" }, + { "Coin", "contract Coin{function isApprovedFor(address _target,address _proxy)constant returns(bool _r){}function isApproved(address _proxy)constant returns(bool _r){}function sendCoinFrom(address _from,uint256 _val,address _to){}function coinBalanceOf(address _a)constant returns(uint256 _r){}function sendCoin(uint256 _val,address _to){}function coinBalance()constant returns(uint256 _r){}function approve(address _a){}}"}, + { "CoinReg", "contract CoinReg{function count()constant returns(uint256 r){}function info(uint256 i)constant returns(address addr,string3 name,uint256 denom){}function register(string3 name,uint256 denom){}function unregister(){}}" }, + { "coin", "#require CoinReg\ncontract coin {function coin(string3 name, uint denom) {CoinReg(Config().lookup(3)).register(name, denom);}}" }, + { "service", "#require Config\ncontract service{function service(uint _n){Config().register(_n, this);}}" }, + { "owned", "contract owned{function owned(){owner = msg.sender;}modifier onlyowner(){if(msg.sender==owner)_}address owner;}" }, + { "mortal", "#require owned\ncontract mortal is owned {function kill() { if (msg.sender == owner) suicide(owner); }}" }, + { "NameReg", "contract NameReg{function register(string32 name){}function addressOf(string32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(string32 name){}}" }, + { "named", "#require Config NameReg\ncontract named {function named(string32 name) {NameReg(Config().lookup(1)).register(name);}}" }, + { "std", "#require owned mortal Config NameReg named" }, + }; + + string sub; + set<string> got; + function<string(string const&)> localExpanded; + localExpanded = [&](string const& s) -> string + { + string ret = s; + for (size_t p = 0; p != string::npos;) + if ((p = ret.find("#require ")) != string::npos) + { + string n = ret.substr(p + 9, ret.find_first_of('\n', p + 9) - p - 9); + ret.replace(p, n.size() + 9, ""); + vector<string> rs; + boost::split(rs, n, boost::is_any_of(" \t,"), boost::token_compress_on); + for (auto const& r: rs) + if (!got.count(r)) + { + if (c_standardSources.count(r)) + sub.append("\n" + localExpanded(c_standardSources.at(r)) + "\n"); + got.insert(r); + } + } + // TODO: remove once we have genesis contracts. + else if ((p = ret.find("Config()")) != string::npos) + ret.replace(p, 8, "Config(0xc6d9d2cd449a754c494264e1809c50e34d64562b)"); + return ret; + }; + return sub + localExpanded(_sourceCode); +} + +////// END: TEMPORARY ONLY + void CompilerStack::compile(bool _optimize) { if (!m_parseSuccessful) @@ -110,12 +188,11 @@ void CompilerStack::compile(bool _optimize) for (ASTPointer<ASTNode> const& node: source->ast->getNodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) { - m_globalContext->setCurrentContract(*contract); shared_ptr<Compiler> compiler = make_shared<Compiler>(_optimize); - compiler->compileContract(*contract, m_globalContext->getMagicVariables(), - contractBytecode); + compiler->compileContract(*contract, contractBytecode); Contract& compiledContract = m_contracts[contract->getName()]; compiledContract.bytecode = compiler->getAssembledBytecode(); + compiledContract.runtimeBytecode = compiler->getRuntimeBytecode(); compiledContract.compiler = move(compiler); contractBytecode[compiledContract.contract] = &compiledContract.bytecode; } @@ -133,6 +210,16 @@ bytes const& CompilerStack::getBytecode(string const& _contractName) const return getContract(_contractName).bytecode; } +bytes const& CompilerStack::getRuntimeBytecode(string const& _contractName) const +{ + return getContract(_contractName).runtimeBytecode; +} + +dev::h256 CompilerStack::getContractCodeHash(string const& _contractName) const +{ + return dev::sha3(getRuntimeBytecode(_contractName)); +} + void CompilerStack::streamAssembly(ostream& _outStream, string const& _contractName) const { getContract(_contractName).compiler->streamAssembly(_outStream); @@ -140,10 +227,15 @@ void CompilerStack::streamAssembly(ostream& _outStream, string const& _contractN string const& CompilerStack::getInterface(string const& _contractName) const { - return getJsonDocumentation(_contractName, DocumentationType::ABI_INTERFACE); + return getMetadata(_contractName, DocumentationType::ABIInterface); } -string const& CompilerStack::getJsonDocumentation(string const& _contractName, DocumentationType _type) const +string const& CompilerStack::getSolidityInterface(string const& _contractName) const +{ + return getMetadata(_contractName, DocumentationType::ABISolidityInterface); +} + +string const& CompilerStack::getMetadata(string const& _contractName, DocumentationType _type) const { if (!m_parseSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); @@ -153,15 +245,18 @@ string const& CompilerStack::getJsonDocumentation(string const& _contractName, D std::unique_ptr<string const>* doc; switch (_type) { - case DocumentationType::NATSPEC_USER: + case DocumentationType::NatspecUser: doc = &contract.userDocumentation; break; - case DocumentationType::NATSPEC_DEV: + case DocumentationType::NatspecDev: doc = &contract.devDocumentation; break; - case DocumentationType::ABI_INTERFACE: + case DocumentationType::ABIInterface: doc = &contract.interface; break; + case DocumentationType::ABISolidityInterface: + doc = &contract.solidityInterface; + break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Illegal documentation type.")); } @@ -198,7 +293,11 @@ void CompilerStack::reset(bool _keepSources) for (auto sourcePair: m_sources) sourcePair.second.reset(); else + { m_sources.clear(); + if (m_addStandardSources) + addSources(StandardSources); + } m_globalContext.reset(); m_sourceOrder.clear(); m_contracts.clear(); @@ -234,16 +333,23 @@ void CompilerStack::resolveImports() swap(m_sourceOrder, sourceOrder); } +std::string CompilerStack::defaultContractName() const +{ + return getContract("").contract->getName(); +} + CompilerStack::Contract const& CompilerStack::getContract(string const& _contractName) const { if (m_contracts.empty()) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found.")); string contractName = _contractName; if (_contractName.empty()) - // try to find the "last contract" - for (ASTPointer<ASTNode> const& node: m_sourceOrder.back()->ast->getNodes()) - if (auto contract = dynamic_cast<ContractDefinition const*>(node.get())) - contractName = contract->getName(); + // try to find some user-supplied contract + for (auto const& it: m_sources) + if (!StandardSources.count(it.first)) + for (ASTPointer<ASTNode> const& node: it.second.ast->getNodes()) + if (auto contract = dynamic_cast<ContractDefinition const*>(node.get())) + contractName = contract->getName(); auto it = m_contracts.find(contractName); if (it == m_contracts.end()) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract " + _contractName + " not found.")); diff --git a/CompilerStack.h b/CompilerStack.h index 358c8fb7..cb4770cd 100644 --- a/CompilerStack.h +++ b/CompilerStack.h @@ -16,6 +16,7 @@ */ /** * @author Christian <c@ethdev.com> + * @author Gav Wood <g@ethdev.com> * @date 2014 * Full-stack compiler that converts a source code string to bytecode. */ @@ -27,6 +28,7 @@ #include <memory> #include <boost/noncopyable.hpp> #include <libdevcore/Common.h> +#include <libdevcore/FixedHash.h> namespace dev { namespace solidity { @@ -41,11 +43,14 @@ class InterfaceHandler; enum class DocumentationType: uint8_t { - NATSPEC_USER = 1, - NATSPEC_DEV, - ABI_INTERFACE + NatspecUser = 1, + NatspecDev, + ABIInterface, + ABISolidityInterface }; +extern const std::map<std::string, std::string> StandardSources; + /** * Easy to use and self-contained Solidity compiler with as few header dependencies as possible. * It holds state and can be used to either step through the compilation stages (and abort e.g. @@ -54,18 +59,21 @@ enum class DocumentationType: uint8_t class CompilerStack: boost::noncopyable { public: - CompilerStack(): m_parseSuccessful(false) {} + /// Creates a new compiler stack. Adds standard sources if @a _addStandardSources. + explicit CompilerStack(bool _addStandardSources = false); /// Adds a source object (e.g. file) to the parser. After this, parse has to be called again. /// @returns true if a source object by the name already existed and was replaced. + void addSources(std::map<std::string, std::string> const& _nameContents) { for (auto const& i: _nameContents) addSource(i.first, i.second); } bool addSource(std::string const& _name, std::string const& _content); void setSource(std::string const& _sourceCode); /// Parses all source units that were added void parse(); - /// Sets the given source code as the only source unit and parses it. + /// Sets the given source code as the only source unit apart from standard sources and parses it. void parse(std::string const& _sourceCode); /// Returns a list of the contract names in the sources. std::vector<std::string> getContractNames() const; + std::string defaultContractName() const; /// Compiles the source units that were previously added and parsed. void compile(bool _optimize = false); @@ -73,7 +81,13 @@ public: /// @returns the compiled bytecode bytes const& compile(std::string const& _sourceCode, bool _optimize = false); + /// @returns the assembled bytecode for a contract. bytes const& getBytecode(std::string const& _contractName = "") const; + /// @returns the runtime bytecode for the contract, i.e. the code that is returned by the constructor. + bytes const& getRuntimeBytecode(std::string const& _contractName = "") const; + /// @returns hash of the runtime bytecode for the contract, i.e. the code that is returned by the constructor. + dev::h256 getContractCodeHash(std::string const& _contractName = "") const; + /// Streams a verbose version of the assembly to @a _outStream. /// Prerequisite: Successful compilation. void streamAssembly(std::ostream& _outStream, std::string const& _contractName = "") const; @@ -81,11 +95,14 @@ public: /// Returns a string representing the contract interface in JSON. /// Prerequisite: Successful call to parse or compile. std::string const& getInterface(std::string const& _contractName = "") const; + /// Returns a string representing the contract interface in Solidity. + /// Prerequisite: Successful call to parse or compile. + std::string const& getSolidityInterface(std::string const& _contractName = "") const; /// Returns a string representing the contract's documentation in JSON. /// Prerequisite: Successful call to parse or compile. /// @param type The type of the documentation to get. - /// Can be one of 3 types defined at @c DocumentationType - std::string const& getJsonDocumentation(std::string const& _contractName, DocumentationType _type) const; + /// Can be one of 4 types defined at @c DocumentationType + std::string const& getMetadata(std::string const& _contractName, DocumentationType _type) const; /// @returns the previously used scanner, useful for counting lines during error reporting. Scanner const& getScanner(std::string const& _sourceName = "") const; @@ -113,23 +130,30 @@ private: struct Contract { - ContractDefinition const* contract; + ContractDefinition const* contract = nullptr; std::shared_ptr<Compiler> compiler; bytes bytecode; + bytes runtimeBytecode; std::shared_ptr<InterfaceHandler> interfaceHandler; mutable std::unique_ptr<std::string const> interface; + mutable std::unique_ptr<std::string const> solidityInterface; mutable std::unique_ptr<std::string const> userDocumentation; mutable std::unique_ptr<std::string const> devDocumentation; Contract(); }; + /// Expand source code with preprocessor-like includes. + /// @todo Replace with better framework. + std::string expanded(std::string const& _sourceCode); + void reset(bool _keepSources = false); void resolveImports(); Contract const& getContract(std::string const& _contractName = "") const; Source const& getSource(std::string const& _sourceName = "") const; + bool m_addStandardSources; ///< If true, standard sources are added. bool m_parseSuccessful; std::map<std::string const, Source> m_sources; std::shared_ptr<GlobalContext> m_globalContext; diff --git a/CompilerUtils.cpp b/CompilerUtils.cpp index 680e9190..047bc6d6 100644 --- a/CompilerUtils.cpp +++ b/CompilerUtils.cpp @@ -31,42 +31,94 @@ namespace dev namespace solidity { -void CompilerUtils::loadFromMemory(unsigned _offset, unsigned _bytes, bool _leftAligned, bool _fromCalldata) +const unsigned int CompilerUtils::dataStartOffset = 4; + +unsigned CompilerUtils::loadFromMemory(unsigned _offset, Type const& _type, + bool _fromCalldata, bool _padToWordBoundaries) { - if (_bytes == 0) - { - m_context << u256(0); - return; - } - eth::Instruction load = _fromCalldata ? eth::Instruction::CALLDATALOAD : eth::Instruction::MLOAD; - solAssert(_bytes <= 32, "Memory load of more than 32 bytes requested."); - if (_bytes == 32) - m_context << u256(_offset) << load; - else - { - // load data and add leading or trailing zeros by dividing/multiplying depending on alignment - u256 shiftFactor = u256(1) << ((32 - _bytes) * 8); - m_context << shiftFactor; - if (_leftAligned) - m_context << eth::Instruction::DUP1; - m_context << u256(_offset) << load << eth::Instruction::DIV; - if (_leftAligned) - m_context << eth::Instruction::MUL; - } + solAssert(_type.getCategory() != Type::Category::ByteArray, "Unable to statically load dynamic type."); + m_context << u256(_offset); + return loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries); +} + +void CompilerUtils::loadFromMemoryDynamic(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) +{ + solAssert(_type.getCategory() != Type::Category::ByteArray, "Byte arrays not yet implemented."); + m_context << eth::Instruction::DUP1; + unsigned numBytes = loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries); + // update memory counter + for (unsigned i = 0; i < _type.getSizeOnStack(); ++i) + m_context << eth::swapInstruction(1 + i); + m_context << u256(numBytes) << eth::Instruction::ADD; +} + + +unsigned CompilerUtils::storeInMemory(unsigned _offset, Type const& _type, bool _padToWordBoundaries) +{ + solAssert(_type.getCategory() != Type::Category::ByteArray, "Unable to statically store dynamic type."); + unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); + if (numBytes > 0) + m_context << u256(_offset) << eth::Instruction::MSTORE; + return numBytes; } -void CompilerUtils::storeInMemory(unsigned _offset, unsigned _bytes, bool _leftAligned) +void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries) { - if (_bytes == 0) + if (_type.getCategory() == Type::Category::ByteArray) { - m_context << eth::Instruction::POP; - return; + auto const& type = dynamic_cast<ByteArrayType const&>(_type); + + if (type.getLocation() == ByteArrayType::Location::CallData) + { + // stack: target source_offset source_len + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5 + // stack: target source_offset source_len source_len source_offset target + << eth::Instruction::CALLDATACOPY + << eth::Instruction::DUP3 << eth::Instruction::ADD + << eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::POP; + } + else + { + solAssert(type.getLocation() == ByteArrayType::Location::Storage, "Memory byte arrays not yet implemented."); + m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; + // stack here: memory_offset storage_offset length_bytes + // jump to end if length is zero + m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO; + eth::AssemblyItem loopEnd = m_context.newTag(); + m_context.appendConditionalJumpTo(loopEnd); + // compute memory end offset + m_context << eth::Instruction::DUP3 << eth::Instruction::ADD << eth::Instruction::SWAP2; + // actual array data is stored at SHA3(storage_offset) + m_context << eth::Instruction::SWAP1; + CompilerUtils(m_context).computeHashStatic(); + m_context << eth::Instruction::SWAP1; + + // stack here: memory_end_offset storage_data_offset memory_offset + eth::AssemblyItem loopStart = m_context.newTag(); + m_context << loopStart + // load and store + << eth::Instruction::DUP2 << eth::Instruction::SLOAD + << eth::Instruction::DUP2 << eth::Instruction::MSTORE + // increment storage_data_offset by 1 + << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD + // increment memory offset by 32 + << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD + // check for loop condition + << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::GT; + m_context.appendConditionalJumpTo(loopStart); + m_context << loopEnd << eth::Instruction::POP << eth::Instruction::POP; + } + } + else + { + unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); + if (numBytes > 0) + { + solAssert(_type.getSizeOnStack() == 1, "Memory store of types with stack size != 1 not implemented."); + m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; + m_context << u256(numBytes) << eth::Instruction::ADD; + } } - solAssert(_bytes <= 32, "Memory store of more than 32 bytes requested."); - if (_bytes != 32 && !_leftAligned) - // shift the value accordingly before storing - m_context << (u256(1) << ((32 - _bytes) * 8)) << eth::Instruction::MUL; - m_context << u256(_offset) << eth::Instruction::MSTORE; } void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) @@ -105,5 +157,220 @@ unsigned CompilerUtils::getSizeOnStack(vector<shared_ptr<Type const>> const& _va return size; } +void CompilerUtils::computeHashStatic(Type const& _type, bool _padToWordBoundaries) +{ + unsigned length = storeInMemory(0, _type, _padToWordBoundaries); + m_context << u256(length) << u256(0) << eth::Instruction::SHA3; +} + +void CompilerUtils::copyByteArrayToStorage(ByteArrayType const& _targetType, + ByteArrayType const& _sourceType) const +{ + // stack layout: [source_ref] target_ref (top) + // need to leave target_ref on the stack at the end + solAssert(_targetType.getLocation() == ByteArrayType::Location::Storage, ""); + + switch (_sourceType.getLocation()) + { + case ByteArrayType::Location::CallData: + { + // This also assumes that after "length" we only have zeros, i.e. it cannot be used to + // slice a byte array from calldata. + + // stack: source_offset source_len target_ref + // fetch old length and convert to words + m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; + m_context << u256(31) << eth::Instruction::ADD + << u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + // stack here: source_offset source_len target_ref target_length_words + // actual array data is stored at SHA3(storage_offset) + m_context << eth::Instruction::DUP2; + CompilerUtils(m_context).computeHashStatic(); + // compute target_data_end + m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2 << eth::Instruction::ADD + << eth::Instruction::SWAP1; + // stack here: source_offset source_len target_ref target_data_end target_data_ref + // store length (in bytes) + m_context << eth::Instruction::DUP4 << eth::Instruction::DUP1 << eth::Instruction::DUP5 + << eth::Instruction::SSTORE; + // jump to end if length is zero + m_context << eth::Instruction::ISZERO; + eth::AssemblyItem copyLoopEnd = m_context.newTag(); + m_context.appendConditionalJumpTo(copyLoopEnd); + // store start offset + m_context << eth::Instruction::DUP5; + // stack now: source_offset source_len target_ref target_data_end target_data_ref calldata_offset + eth::AssemblyItem copyLoopStart = m_context.newTag(); + m_context << copyLoopStart + // copy from calldata and store + << eth::Instruction::DUP1 << eth::Instruction::CALLDATALOAD + << eth::Instruction::DUP3 << eth::Instruction::SSTORE + // increment target_data_ref by 1 + << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD + // increment calldata_offset by 32 + << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD + // check for loop condition + << eth::Instruction::DUP1 << eth::Instruction::DUP6 << eth::Instruction::GT; + m_context.appendConditionalJumpTo(copyLoopStart); + m_context << eth::Instruction::POP; + m_context << copyLoopEnd; + + // now clear leftover bytes of the old value + // stack now: source_offset source_len target_ref target_data_end target_data_ref + clearStorageLoop(); + // stack now: source_offset source_len target_ref target_data_end + + m_context << eth::Instruction::POP << eth::Instruction::SWAP2 + << eth::Instruction::POP << eth::Instruction::POP; + break; + } + case ByteArrayType::Location::Storage: + { + // this copies source to target and also clears target if it was larger + + // stack: source_ref target_ref + // store target_ref + m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; + // fetch lengthes + m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP2 + << eth::Instruction::DUP1 << eth::Instruction::SLOAD; + // stack: target_ref target_len_bytes target_ref source_ref source_len_bytes + // store new target length + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SSTORE; + // compute hashes (data positions) + m_context << eth::Instruction::SWAP2; + CompilerUtils(m_context).computeHashStatic(); + m_context << eth::Instruction::SWAP1; + CompilerUtils(m_context).computeHashStatic(); + // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos + // convert lengthes from bytes to storage slots + m_context << u256(31) << u256(32) << eth::Instruction::DUP1 << eth::Instruction::DUP3 + << eth::Instruction::DUP8 << eth::Instruction::ADD << eth::Instruction::DIV + << eth::Instruction::SWAP2 + << eth::Instruction::DUP6 << eth::Instruction::ADD << eth::Instruction::DIV; + // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len + // @todo we might be able to go without a third counter + m_context << u256(0); + // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len counter + eth::AssemblyItem copyLoopStart = m_context.newTag(); + m_context << copyLoopStart; + // check for loop condition + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 + << eth::Instruction::GT << eth::Instruction::ISZERO; + eth::AssemblyItem copyLoopEnd = m_context.newTag(); + m_context.appendConditionalJumpTo(copyLoopEnd); + // copy + m_context << eth::Instruction::DUP4 << eth::Instruction::DUP2 << eth::Instruction::ADD + << eth::Instruction::SLOAD + << eth::Instruction::DUP6 << eth::Instruction::DUP3 << eth::Instruction::ADD + << eth::Instruction::SSTORE; + // increment + m_context << u256(1) << eth::Instruction::ADD; + m_context.appendJumpTo(copyLoopStart); + m_context << copyLoopEnd; + + // zero-out leftovers in target + // stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len counter + // add counter to target_data_pos + m_context << eth::Instruction::DUP5 << eth::Instruction::ADD + << eth::Instruction::SWAP5 << eth::Instruction::POP; + // stack: target_ref target_len_bytes target_data_pos_updated target_data_pos source_data_pos target_len source_len + // add length to target_data_pos to get target_data_end + m_context << eth::Instruction::POP << eth::Instruction::DUP3 << eth::Instruction::ADD + << eth::Instruction::SWAP4 + << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; + // stack: target_ref target_data_end target_data_pos_updated + clearStorageLoop(); + m_context << eth::Instruction::POP; + break; + } + default: + solAssert(false, "Given byte array location not implemented."); + } +} + +unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) +{ + unsigned _encodedSize = _type.getCalldataEncodedSize(); + unsigned numBytes = _padToWordBoundaries ? getPaddedSize(_encodedSize) : _encodedSize; + bool leftAligned = _type.getCategory() == Type::Category::String; + if (numBytes == 0) + m_context << eth::Instruction::POP << u256(0); + else + { + solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested."); + m_context << (_fromCalldata ? eth::Instruction::CALLDATALOAD : eth::Instruction::MLOAD); + if (numBytes != 32) + { + // add leading or trailing zeros by dividing/multiplying depending on alignment + u256 shiftFactor = u256(1) << ((32 - numBytes) * 8); + m_context << shiftFactor << eth::Instruction::SWAP1 << eth::Instruction::DIV; + if (leftAligned) + m_context << shiftFactor << eth::Instruction::MUL; + } + } + + return numBytes; +} + +void CompilerUtils::clearByteArray(ByteArrayType const& _type) const +{ + solAssert(_type.getLocation() == ByteArrayType::Location::Storage, ""); + + // fetch length + m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; + // set length to zero + m_context << u256(0) << eth::Instruction::DUP3 << eth::Instruction::SSTORE; + // convert length from bytes to storage slots + m_context << u256(31) << eth::Instruction::ADD + << u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + // compute data positions + m_context << eth::Instruction::SWAP1; + CompilerUtils(m_context).computeHashStatic(); + // stack: len data_pos + m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD + << eth::Instruction::SWAP1; + clearStorageLoop(); + // cleanup + m_context << eth::Instruction::POP; +} + +unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const +{ + unsigned _encodedSize = _type.getCalldataEncodedSize(); + unsigned numBytes = _padToWordBoundaries ? getPaddedSize(_encodedSize) : _encodedSize; + bool leftAligned = _type.getCategory() == Type::Category::String; + if (numBytes == 0) + m_context << eth::Instruction::POP; + else + { + solAssert(numBytes <= 32, "Memory store of more than 32 bytes requested."); + if (numBytes != 32 && !leftAligned && !_padToWordBoundaries) + // shift the value accordingly before storing + m_context << (u256(1) << ((32 - numBytes) * 8)) << eth::Instruction::MUL; + } + return numBytes; +} + +void CompilerUtils::clearStorageLoop() const +{ + // stack: end_pos pos + eth::AssemblyItem loopStart = m_context.newTag(); + m_context << loopStart; + // check for loop condition + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 + << eth::Instruction::GT << eth::Instruction::ISZERO; + eth::AssemblyItem zeroLoopEnd = m_context.newTag(); + m_context.appendConditionalJumpTo(zeroLoopEnd); + // zero out + m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE; + // increment + m_context << u256(1) << eth::Instruction::ADD; + m_context.appendJumpTo(loopStart); + // cleanup + m_context << zeroLoopEnd; + m_context << eth::Instruction::POP; +} + } } diff --git a/CompilerUtils.h b/CompilerUtils.h index 928f0e2d..5369d3bf 100644 --- a/CompilerUtils.h +++ b/CompilerUtils.h @@ -37,15 +37,31 @@ public: /// Loads data from memory to the stack. /// @param _offset offset in memory (or calldata) - /// @param _bytes number of bytes to load - /// @param _leftAligned if true, store left aligned on stack (otherwise right aligned) + /// @param _type data type to load /// @param _fromCalldata if true, load from calldata, not from memory - void loadFromMemory(unsigned _offset, unsigned _bytes = 32, bool _leftAligned = false, bool _fromCalldata = false); + /// @param _padToWordBoundaries if true, assume the data is padded to word (32 byte) boundaries + /// @returns the number of bytes consumed in memory. + unsigned loadFromMemory(unsigned _offset, Type const& _type = IntegerType(256), + bool _fromCalldata = false, bool _padToWordBoundaries = false); + /// Dynamic version of @see loadFromMemory, expects the memory offset on the stack. + /// Stack pre: memory_offset + /// Stack post: value... (memory_offset+length) + void loadFromMemoryDynamic(Type const& _type, bool _fromCalldata = false, bool _padToWordBoundaries = true); /// Stores data from stack in memory. /// @param _offset offset in memory - /// @param _bytes number of bytes to store - /// @param _leftAligned if true, data is left aligned on stack (otherwise right aligned) - void storeInMemory(unsigned _offset, unsigned _bytes = 32, bool _leftAligned = false); + /// @param _type type of the data on the stack + /// @param _padToWordBoundaries if true, pad the data to word (32 byte) boundaries + /// @returns the number of bytes written to memory (can be different from _bytes if + /// _padToWordBoundaries is true) + unsigned storeInMemory(unsigned _offset, Type const& _type = IntegerType(256), bool _padToWordBoundaries = false); + /// Dynamic version of @see storeInMemory, expects the memory offset below the value on the stack + /// and also updates that. + /// Stack pre: memory_offset value... + /// Stack post: (memory_offset+length) + void storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries = true); + /// @returns _size rounded up to the next multiple of 32 (the number of bytes occupied in the + /// padded calldata) + static unsigned getPaddedSize(unsigned _size) { return ((_size + 31) / 32) * 32; } /// Moves the value that is at the top of the stack to a stack variable. void moveToStackVariable(VariableDeclaration const& _variable); @@ -58,10 +74,38 @@ public: static unsigned getSizeOnStack(std::vector<T> const& _variables); static unsigned getSizeOnStack(std::vector<std::shared_ptr<Type const>> const& _variableTypes); + /// Appends code that computes tha SHA3 hash of the topmost stack element of type @a _type. + /// If @a _pad is set, padds the type to muliples of 32 bytes. + /// @note Only works for types of fixed size. + void computeHashStatic(Type const& _type = IntegerType(256), bool _padToWordBoundaries = false); + + /// Copies a byte array to a byte array in storage. + /// Stack pre: [source_reference] target_reference + /// Stack post: target_reference + void copyByteArrayToStorage(ByteArrayType const& _targetType, ByteArrayType const& _sourceType) const; + /// Clears the length and data elements of the byte array referenced on the stack. + /// Stack pre: reference + /// Stack post: + void clearByteArray(ByteArrayType const& _type) const; + + /// Bytes we need to the start of call data. + /// - The size in bytes of the function (hash) identifier. + static const unsigned int dataStartOffset; + private: + /// Prepares the given type for storing in memory by shifting it if necessary. + unsigned prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const; + /// Loads type from memory assuming memory offset is on stack top. + unsigned loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries); + /// Appends a loop that clears a sequence of storage slots (excluding end). + /// Stack pre: end_ref start_ref + /// Stack post: end_ref + void clearStorageLoop() const; + CompilerContext& m_context; }; + template <class T> unsigned CompilerUtils::getSizeOnStack(std::vector<T> const& _variables) { diff --git a/DeclarationContainer.cpp b/DeclarationContainer.cpp index c7081bc7..2594d428 100644 --- a/DeclarationContainer.cpp +++ b/DeclarationContainer.cpp @@ -28,16 +28,25 @@ namespace dev namespace solidity { -bool DeclarationContainer::registerDeclaration(Declaration const& _declaration, bool _update) +bool DeclarationContainer::registerDeclaration(Declaration const& _declaration, bool _invisible, bool _update) { - if (!_update && m_declarations.find(_declaration.getName()) != m_declarations.end()) + ASTString const& name(_declaration.getName()); + if (name.empty()) + return true; + + if (!_update && (m_declarations.count(name) || m_invisibleDeclarations.count(name))) return false; - m_declarations[_declaration.getName()] = &_declaration; + + if (_invisible) + m_invisibleDeclarations.insert(name); + else + m_declarations[name] = &_declaration; return true; } Declaration const* DeclarationContainer::resolveName(ASTString const& _name, bool _recursive) const { + solAssert(!_name.empty(), "Attempt to resolve empty name."); auto result = m_declarations.find(_name); if (result != m_declarations.end()) return result->second; diff --git a/DeclarationContainer.h b/DeclarationContainer.h index c0a0b42c..f70881f5 100644 --- a/DeclarationContainer.h +++ b/DeclarationContainer.h @@ -23,6 +23,7 @@ #pragma once #include <map> +#include <set> #include <boost/noncopyable.hpp> #include <libsolidity/ASTForward.h> @@ -42,16 +43,20 @@ public: explicit DeclarationContainer(Declaration const* _enclosingDeclaration = nullptr, DeclarationContainer const* _enclosingContainer = nullptr): m_enclosingDeclaration(_enclosingDeclaration), m_enclosingContainer(_enclosingContainer) {} - /// Registers the declaration in the scope unless its name is already declared. Returns true iff - /// it was not yet declared. - bool registerDeclaration(Declaration const& _declaration, bool _update = false); + /// Registers the declaration in the scope unless its name is already declared or the name is empty. + /// @param _invisible if true, registers the declaration, reports name clashes but does not return it in @a resolveName + /// @param _update if true, replaces a potential declaration that is already present + /// @returns false if the name was already declared. + bool registerDeclaration(Declaration const& _declaration, bool _invisible = false, bool _update = false); Declaration const* resolveName(ASTString const& _name, bool _recursive = false) const; Declaration const* getEnclosingDeclaration() const { return m_enclosingDeclaration; } + std::map<ASTString, Declaration const*> const& getDeclarations() const { return m_declarations; } private: Declaration const* m_enclosingDeclaration; DeclarationContainer const* m_enclosingContainer; std::map<ASTString, Declaration const*> m_declarations; + std::set<ASTString> m_invisibleDeclarations; }; } diff --git a/Exceptions.h b/Exceptions.h index 14f91977..0b25abee 100644 --- a/Exceptions.h +++ b/Exceptions.h @@ -38,7 +38,7 @@ struct CompilerError: virtual Exception {}; struct InternalCompilerError: virtual Exception {}; struct DocstringParsingError: virtual Exception {}; -typedef boost::error_info<struct tag_sourceLocation, Location> errinfo_sourceLocation; +using errinfo_sourceLocation = boost::error_info<struct tag_sourceLocation, Location>; } } diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp index cf641935..a8bc53e0 100644 --- a/ExpressionCompiler.cpp +++ b/ExpressionCompiler.cpp @@ -22,7 +22,9 @@ #include <utility> #include <numeric> +#include <boost/range/adaptor/reversed.hpp> #include <libdevcore/Common.h> +#include <libdevcrypto/SHA3.h> #include <libsolidity/AST.h> #include <libsolidity/ExpressionCompiler.h> #include <libsolidity/CompilerContext.h> @@ -41,64 +43,75 @@ void ExpressionCompiler::compileExpression(CompilerContext& _context, Expression _expression.accept(compiler); } -void ExpressionCompiler::appendTypeConversion(CompilerContext& _context, - Type const& _typeOnStack, Type const& _targetType) +void ExpressionCompiler::appendTypeConversion(CompilerContext& _context, Type const& _typeOnStack, + Type const& _targetType, bool _cleanupNeeded) { ExpressionCompiler compiler(_context); - compiler.appendTypeConversion(_typeOnStack, _targetType); + compiler.appendTypeConversion(_typeOnStack, _targetType, _cleanupNeeded); +} + +void ExpressionCompiler::appendStateVariableAccessor(CompilerContext& _context, VariableDeclaration const& _varDecl, bool _optimize) +{ + ExpressionCompiler compiler(_context, _optimize); + compiler.appendStateVariableAccessor(_varDecl); } bool ExpressionCompiler::visit(Assignment const& _assignment) { _assignment.getRightHandSide().accept(*this); - appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); + if (_assignment.getType()->isValueType()) + appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); _assignment.getLeftHandSide().accept(*this); solAssert(m_currentLValue.isValid(), "LValue not retrieved."); Token::Value op = _assignment.getAssignmentOperator(); - if (op != Token::ASSIGN) // compound assignment + if (op != Token::Assign) // compound assignment { + solAssert(_assignment.getType()->isValueType(), "Compound operators not implemented for non-value types."); if (m_currentLValue.storesReferenceOnStack()) m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; - m_currentLValue.retrieveValue(_assignment, true); + m_currentLValue.retrieveValue(_assignment.getLocation(), true); appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType()); if (m_currentLValue.storesReferenceOnStack()) m_context << eth::Instruction::SWAP1; } - m_currentLValue.storeValue(_assignment); + m_currentLValue.storeValue(*_assignment.getRightHandSide().getType(), _assignment.getLocation()); m_currentLValue.reset(); return false; } -void ExpressionCompiler::endVisit(UnaryOperation const& _unaryOperation) +bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) { //@todo type checking and creating code for an operator should be in the same place: // the operator should know how to convert itself and to which types it applies, so // put this code together with "Type::acceptsBinary/UnaryOperator" into a class that // represents the operator + if (_unaryOperation.getType()->getCategory() == Type::Category::IntegerConstant) + { + m_context << _unaryOperation.getType()->literalValue(nullptr); + return false; + } + + _unaryOperation.getSubExpression().accept(*this); + switch (_unaryOperation.getOperator()) { - case Token::NOT: // ! + case Token::Not: // ! m_context << eth::Instruction::ISZERO; break; - case Token::BIT_NOT: // ~ + case Token::BitNot: // ~ m_context << eth::Instruction::NOT; break; - case Token::DELETE: // delete - // @todo semantics change for complex types + case Token::Delete: // delete solAssert(m_currentLValue.isValid(), "LValue not retrieved."); - - m_context << u256(0); - if (m_currentLValue.storesReferenceOnStack()) - m_context << eth::Instruction::SWAP1; - m_currentLValue.storeValue(_unaryOperation); + m_currentLValue.setToZero(_unaryOperation.getLocation()); m_currentLValue.reset(); break; - case Token::INC: // ++ (pre- or postfix) - case Token::DEC: // -- (pre- or postfix) + case Token::Inc: // ++ (pre- or postfix) + case Token::Dec: // -- (pre- or postfix) solAssert(m_currentLValue.isValid(), "LValue not retrieved."); - m_currentLValue.retrieveValue(_unaryOperation); + m_currentLValue.retrieveValue(_unaryOperation.getLocation()); if (!_unaryOperation.isPrefixOperation()) { if (m_currentLValue.storesReferenceOnStack()) @@ -107,7 +120,7 @@ void ExpressionCompiler::endVisit(UnaryOperation const& _unaryOperation) m_context << eth::Instruction::DUP1; } m_context << u256(1); - if (_unaryOperation.getOperator() == Token::INC) + if (_unaryOperation.getOperator() == Token::Inc) m_context << eth::Instruction::ADD; else m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; // @todo avoid the swap @@ -115,19 +128,21 @@ void ExpressionCompiler::endVisit(UnaryOperation const& _unaryOperation) // Stack for postfix: *ref [ref] (*ref)+-1 if (m_currentLValue.storesReferenceOnStack()) m_context << eth::Instruction::SWAP1; - m_currentLValue.storeValue(_unaryOperation, !_unaryOperation.isPrefixOperation()); + m_currentLValue.storeValue(*_unaryOperation.getType(), _unaryOperation.getLocation(), + !_unaryOperation.isPrefixOperation()); m_currentLValue.reset(); break; - case Token::ADD: // + + case Token::Add: // + // unary add, so basically no-op break; - case Token::SUB: // - + case Token::Sub: // - m_context << u256(0) << eth::Instruction::SUB; break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid unary operator: " + string(Token::toString(_unaryOperation.getOperator())))); } + return false; } bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) @@ -135,21 +150,23 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) Expression const& leftExpression = _binaryOperation.getLeftExpression(); Expression const& rightExpression = _binaryOperation.getRightExpression(); Type const& commonType = _binaryOperation.getCommonType(); - Token::Value const op = _binaryOperation.getOperator(); + Token::Value const c_op = _binaryOperation.getOperator(); - if (op == Token::AND || op == Token::OR) // special case: short-circuiting + if (c_op == Token::And || c_op == Token::Or) // special case: short-circuiting appendAndOrOperatorCode(_binaryOperation); + else if (commonType.getCategory() == Type::Category::IntegerConstant) + m_context << commonType.literalValue(nullptr); else { - bool cleanupNeeded = false; - if (commonType.getCategory() == Type::Category::INTEGER) - if (Token::isCompareOp(op) || op == Token::DIV || op == Token::MOD) - cleanupNeeded = true; + bool cleanupNeeded = commonType.getCategory() == Type::Category::Integer && + (Token::isCompareOp(c_op) || c_op == Token::Div || c_op == Token::Mod); // for commutative operators, push the literal as late as possible to allow improved optimization - //@todo this has to be extended for literal expressions - bool swap = (m_optimize && Token::isCommutativeOp(op) && dynamic_cast<Literal const*>(&rightExpression) - && !dynamic_cast<Literal const*>(&leftExpression)); + auto isLiteral = [](Expression const& _e) + { + return dynamic_cast<Literal const*>(&_e) || _e.getType()->getCategory() == Type::Category::IntegerConstant; + }; + bool swap = m_optimize && Token::isCommutativeOp(c_op) && isLiteral(rightExpression) && !isLiteral(leftExpression); if (swap) { leftExpression.accept(*this); @@ -164,10 +181,10 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) leftExpression.accept(*this); appendTypeConversion(*leftExpression.getType(), commonType, cleanupNeeded); } - if (Token::isCompareOp(op)) - appendCompareOperatorCode(op, commonType); + if (Token::isCompareOp(c_op)) + appendCompareOperatorCode(c_op, commonType); else - appendOrdinaryBinaryOperatorCode(op, commonType); + appendOrdinaryBinaryOperatorCode(c_op, commonType); } // do not visit the child nodes, we already did that explicitly @@ -181,25 +198,39 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { //@todo struct construction solAssert(_functionCall.getArguments().size() == 1, ""); + solAssert(_functionCall.getNames().empty(), ""); Expression const& firstArgument = *_functionCall.getArguments().front(); firstArgument.accept(*this); - if (firstArgument.getType()->getCategory() == Type::Category::CONTRACT && - _functionCall.getType()->getCategory() == Type::Category::INTEGER) - { - // explicit type conversion contract -> address, nothing to do. - } - else - appendTypeConversion(*firstArgument.getType(), *_functionCall.getType()); + appendTypeConversion(*firstArgument.getType(), *_functionCall.getType()); } else { FunctionType const& function = dynamic_cast<FunctionType const&>(*_functionCall.getExpression().getType()); - vector<ASTPointer<Expression const>> arguments = _functionCall.getArguments(); - solAssert(arguments.size() == function.getParameterTypes().size(), ""); + TypePointers const& parameterTypes = function.getParameterTypes(); + vector<ASTPointer<Expression const>> const& callArguments = _functionCall.getArguments(); + vector<ASTPointer<ASTString>> const& callArgumentNames = _functionCall.getNames(); + if (!function.takesArbitraryParameters()) + solAssert(callArguments.size() == parameterTypes.size(), ""); + + vector<ASTPointer<Expression const>> arguments; + if (callArgumentNames.empty()) + // normal arguments + arguments = callArguments; + else + // named arguments + for (auto const& parameterName: function.getParameterNames()) + { + bool found = false; + for (size_t j = 0; j < callArgumentNames.size() && !found; j++) + if ((found = (parameterName == *callArgumentNames[j]))) + // we found the actual parameter position + arguments.push_back(callArguments[j]); + solAssert(found, ""); + } switch (function.getLocation()) { - case Location::INTERNAL: + case Location::Internal: { // Calling convention: Caller pushes return address and arguments // Callee removes them and pushes return values @@ -217,7 +248,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) unsigned returnParametersSize = CompilerUtils::getSizeOnStack(function.getReturnParameterTypes()); // callee adds return parameters, but removes arguments and return label - m_context.adjustStackOffset(returnParametersSize - CompilerUtils::getSizeOnStack(arguments) - 1); + m_context.adjustStackOffset(returnParametersSize - CompilerUtils::getSizeOnStack(function.getParameterTypes()) - 1); // @todo for now, the return value of a function is its first return value, so remove // all others @@ -225,50 +256,143 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) CompilerUtils(m_context).popStackElement(*function.getReturnParameterTypes()[i]); break; } - case Location::EXTERNAL: - case Location::BARE: + case Location::External: + case Location::Bare: + _functionCall.getExpression().accept(*this); + appendExternalFunctionCall(function, arguments, function.getLocation() == Location::Bare); + break; + case Location::Creation: { - FunctionCallOptions options; - options.bare = function.getLocation() == Location::BARE; - options.obtainAddress = [&]() { _functionCall.getExpression().accept(*this); }; - appendExternalFunctionCall(function, arguments, options); + _functionCall.getExpression().accept(*this); + solAssert(!function.gasSet(), "Gas limit set for contract creation."); + solAssert(function.getReturnParameterTypes().size() == 1, ""); + ContractDefinition const& contract = dynamic_cast<ContractType const&>( + *function.getReturnParameterTypes().front()).getContractDefinition(); + // copy the contract's code into memory + bytes const& bytecode = m_context.getCompiledContract(contract); + m_context << u256(bytecode.size()); + //@todo could be done by actually appending the Assembly, but then we probably need to compile + // multiple times. Will revisit once external fuctions are inlined. + m_context.appendData(bytecode); + //@todo copy to memory position 0, shift as soon as we use memory + m_context << u256(0) << eth::Instruction::CODECOPY; + + m_context << u256(bytecode.size()); + appendArgumentsCopyToMemory(arguments, function.getParameterTypes()); + // size, offset, endowment + m_context << u256(0); + if (function.valueSet()) + m_context << eth::dupInstruction(3); + else + m_context << u256(0); + m_context << eth::Instruction::CREATE; + if (function.valueSet()) + m_context << eth::swapInstruction(1) << eth::Instruction::POP; break; } - case Location::SEND: + case Location::SetGas: { - FunctionCallOptions options; - options.bare = true; - options.obtainAddress = [&]() { _functionCall.getExpression().accept(*this); }; - options.obtainValue = [&]() { arguments.front()->accept(*this); }; - appendExternalFunctionCall(FunctionType({}, {}, Location::EXTERNAL), {}, options); + // stack layout: contract_address function_id [gas] [value] + _functionCall.getExpression().accept(*this); + arguments.front()->accept(*this); + appendTypeConversion(*arguments.front()->getType(), IntegerType(256), true); + // Note that function is not the original function, but the ".gas" function. + // Its values of gasSet and valueSet is equal to the original function's though. + unsigned stackDepth = (function.gasSet() ? 1 : 0) + (function.valueSet() ? 1 : 0); + if (stackDepth > 0) + m_context << eth::swapInstruction(stackDepth); + if (function.gasSet()) + m_context << eth::Instruction::POP; break; } - case Location::SUICIDE: + case Location::SetValue: + // stack layout: contract_address function_id [gas] [value] + _functionCall.getExpression().accept(*this); + // Note that function is not the original function, but the ".value" function. + // Its values of gasSet and valueSet is equal to the original function's though. + if (function.valueSet()) + m_context << eth::Instruction::POP; + arguments.front()->accept(*this); + break; + case Location::Send: + _functionCall.getExpression().accept(*this); + m_context << u256(0); // 0 gas, we do not want to execute code + arguments.front()->accept(*this); + appendTypeConversion(*arguments.front()->getType(), + *function.getParameterTypes().front(), true); + appendExternalFunctionCall(FunctionType(TypePointers{}, TypePointers{}, + Location::External, false, true, true), {}, true); + break; + case Location::Suicide: arguments.front()->accept(*this); - //@todo might not be necessary appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true); m_context << eth::Instruction::SUICIDE; break; case Location::SHA3: - arguments.front()->accept(*this); - appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true); - // @todo move this once we actually use memory - CompilerUtils(m_context).storeInMemory(0); - m_context << u256(32) << u256(0) << eth::Instruction::SHA3; + { + m_context << u256(0); + appendArgumentsCopyToMemory(arguments, TypePointers(), function.padArguments()); + m_context << u256(0) << eth::Instruction::SHA3; + break; + } + case Location::Log0: + case Location::Log1: + case Location::Log2: + case Location::Log3: + case Location::Log4: + { + unsigned logNumber = int(function.getLocation()) - int(Location::Log0); + for (unsigned arg = logNumber; arg > 0; --arg) + { + arguments[arg]->accept(*this); + appendTypeConversion(*arguments[arg]->getType(), *function.getParameterTypes()[arg], true); + } + m_context << u256(0); + appendExpressionCopyToMemory(*function.getParameterTypes().front(), *arguments.front()); + m_context << u256(0) << eth::logInstruction(logNumber); + break; + } + case Location::Event: + { + _functionCall.getExpression().accept(*this); + auto const& event = dynamic_cast<EventDefinition const&>(function.getDeclaration()); + unsigned numIndexed = 0; + // All indexed arguments go to the stack + for (unsigned arg = arguments.size(); arg > 0; --arg) + if (event.getParameters()[arg - 1]->isIndexed()) + { + ++numIndexed; + arguments[arg - 1]->accept(*this); + appendTypeConversion(*arguments[arg - 1]->getType(), + *function.getParameterTypes()[arg - 1], true); + } + m_context << u256(h256::Arith(dev::sha3(function.getCanonicalSignature(event.getName())))); + ++numIndexed; + solAssert(numIndexed <= 4, "Too many indexed arguments."); + // Copy all non-indexed arguments to memory (data) + m_context << u256(0); + for (unsigned arg = 0; arg < arguments.size(); ++arg) + if (!event.getParameters()[arg]->isIndexed()) + appendExpressionCopyToMemory(*function.getParameterTypes()[arg], *arguments[arg]); + m_context << u256(0) << eth::logInstruction(numIndexed); break; - case Location::ECRECOVER: + } + case Location::BlockHash: + { + arguments[0]->accept(*this); + appendTypeConversion(*arguments[0]->getType(), *function.getParameterTypes()[0], true); + m_context << eth::Instruction::BLOCKHASH; + break; + } + case Location::ECRecover: case Location::SHA256: case Location::RIPEMD160: { - static const map<Location, u256> contractAddresses{{Location::ECRECOVER, 1}, + static const map<Location, u256> contractAddresses{{Location::ECRecover, 1}, {Location::SHA256, 2}, {Location::RIPEMD160, 3}}; - u256 contractAddress = contractAddresses.find(function.getLocation())->second; - FunctionCallOptions options; - options.bare = true; - options.obtainAddress = [&]() { m_context << contractAddress; }; - options.packDensely = false; - appendExternalFunctionCall(function, arguments, options); + m_context << contractAddresses.find(function.getLocation())->second; + appendExternalFunctionCall(function, arguments, true); break; } default: @@ -278,39 +402,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) return false; } -bool ExpressionCompiler::visit(NewExpression const& _newExpression) -{ - ContractType const* type = dynamic_cast<ContractType const*>(_newExpression.getType().get()); - solAssert(type, ""); - TypePointers const& types = type->getConstructorType()->getParameterTypes(); - vector<ASTPointer<Expression const>> arguments = _newExpression.getArguments(); - solAssert(arguments.size() == types.size(), ""); - - // copy the contracts code into memory - bytes const& bytecode = m_context.getCompiledContract(*_newExpression.getContract()); - m_context << u256(bytecode.size()); - //@todo could be done by actually appending the Assembly, but then we probably need to compile - // multiple times. Will revisit once external fuctions are inlined. - m_context.appendData(bytecode); - //@todo copy to memory position 0, shift as soon as we use memory - m_context << u256(0) << eth::Instruction::CODECOPY; - - unsigned dataOffset = bytecode.size(); - for (unsigned i = 0; i < arguments.size(); ++i) - { - arguments[i]->accept(*this); - appendTypeConversion(*arguments[i]->getType(), *types[i]); - unsigned const numBytes = types[i]->getCalldataEncodedSize(); - if (numBytes > 32) - BOOST_THROW_EXCEPTION(CompilerError() - << errinfo_sourceLocation(arguments[i]->getLocation()) - << errinfo_comment("Type " + types[i]->toString() + " not yet supported.")); - bool const leftAligned = types[i]->getCategory() == Type::Category::STRING; - CompilerUtils(m_context).storeInMemory(dataOffset, numBytes, leftAligned); - dataOffset += numBytes; - } - // size, offset, endowment - m_context << u256(dataOffset) << u256(0) << u256(0) << eth::Instruction::CREATE; +bool ExpressionCompiler::visit(NewExpression const&) +{ + // code is created for the function call (CREATION) only return false; } @@ -319,33 +413,51 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) ASTString const& member = _memberAccess.getMemberName(); switch (_memberAccess.getExpression().getType()->getCategory()) { - case Type::Category::INTEGER: + case Type::Category::Contract: + { + bool alsoSearchInteger = false; + ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.getExpression().getType()); + if (type.isSuper()) + m_context << m_context.getSuperFunctionEntryLabel(member, type.getContractDefinition()).pushTag(); + else + { + // ordinary contract type + u256 identifier = type.getFunctionIdentifier(member); + if (identifier != Invalid256) + { + appendTypeConversion(type, IntegerType(0, IntegerType::Modifier::Address), true); + m_context << identifier; + } + else + // not found in contract, search in members inherited from address + alsoSearchInteger = true; + } + if (!alsoSearchInteger) + break; + } + case Type::Category::Integer: if (member == "balance") { appendTypeConversion(*_memberAccess.getExpression().getType(), - IntegerType(0, IntegerType::Modifier::ADDRESS), true); + IntegerType(0, IntegerType::Modifier::Address), true); m_context << eth::Instruction::BALANCE; } else if (member == "send" || member.substr(0, min<size_t>(member.size(), 4)) == "call") appendTypeConversion(*_memberAccess.getExpression().getType(), - IntegerType(0, IntegerType::Modifier::ADDRESS), true); + IntegerType(0, IntegerType::Modifier::Address), true); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer.")); break; - case Type::Category::CONTRACT: - { - ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.getExpression().getType()); - m_context << type.getFunctionIndex(member); + case Type::Category::Function: + solAssert(!!_memberAccess.getExpression().getType()->getMemberType(member), + "Invalid member access to function."); break; - } - case Type::Category::MAGIC: + case Type::Category::Magic: // we can ignore the kind of magic and only look at the name of the member if (member == "coinbase") m_context << eth::Instruction::COINBASE; else if (member == "timestamp") m_context << eth::Instruction::TIMESTAMP; - else if (member == "prevhash") - m_context << eth::Instruction::PREVHASH; else if (member == "difficulty") m_context << eth::Instruction::DIFFICULTY; else if (member == "number") @@ -362,17 +474,64 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) m_context << eth::Instruction::GAS; else if (member == "gasprice") m_context << eth::Instruction::GASPRICE; + else if (member == "data") + m_context << u256(0) << eth::Instruction::CALLDATASIZE; else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member.")); break; - case Type::Category::STRUCT: + case Type::Category::Struct: { StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.getExpression().getType()); m_context << type.getStorageOffsetOfMember(member) << eth::Instruction::ADD; - m_currentLValue = LValue(m_context, LValue::STORAGE, *_memberAccess.getType()); + m_currentLValue = LValue(m_context, LValue::LValueType::Storage, _memberAccess.getType()); m_currentLValue.retrieveValueIfLValueNotRequested(_memberAccess); break; } + case Type::Category::Enum: + { + EnumType const& type = dynamic_cast<EnumType const&>(*_memberAccess.getExpression().getType()); + m_context << type.getMemberValue(_memberAccess.getMemberName()); + break; + } + case Type::Category::TypeType: + { + TypeType const& type = dynamic_cast<TypeType const&>(*_memberAccess.getExpression().getType()); + if (!type.getMembers().getMemberType(member)) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to " + type.toString())); + + if (auto contractType = dynamic_cast<ContractType const*>(type.getActualType().get())) + { + ContractDefinition const& contract = contractType->getContractDefinition(); + for (ASTPointer<FunctionDefinition> const& function: contract.getDefinedFunctions()) + if (function->getName() == member) + { + m_context << m_context.getFunctionEntryLabel(*function).pushTag(); + return; + } + solAssert(false, "Function not found in member access."); + } + else if (auto enumType = dynamic_cast<EnumType const*>(type.getActualType().get())) + m_context << enumType->getMemberValue(_memberAccess.getMemberName()); + break; + } + case Type::Category::ByteArray: + { + solAssert(member == "length", "Illegal bytearray member."); + auto const& type = dynamic_cast<ByteArrayType const&>(*_memberAccess.getExpression().getType()); + switch (type.getLocation()) + { + case ByteArrayType::Location::CallData: + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; + break; + case ByteArrayType::Location::Storage: + m_context << eth::Instruction::SLOAD; + break; + default: + solAssert(false, "Unsupported byte array location."); + break; + } + break; + } default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access to unknown type.")); } @@ -381,16 +540,19 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) { _indexAccess.getBaseExpression().accept(*this); - _indexAccess.getIndexExpression().accept(*this); - appendTypeConversion(*_indexAccess.getIndexExpression().getType(), - *dynamic_cast<MappingType const&>(*_indexAccess.getBaseExpression().getType()).getKeyType(), - true); - // @todo move this once we actually use memory - CompilerUtils(m_context).storeInMemory(0); - CompilerUtils(m_context).storeInMemory(32); - m_context << u256(64) << u256(0) << eth::Instruction::SHA3; - - m_currentLValue = LValue(m_context, LValue::STORAGE, *_indexAccess.getType()); + + Type const& baseType = *_indexAccess.getBaseExpression().getType(); + solAssert(baseType.getCategory() == Type::Category::Mapping, ""); + Type const& keyType = *dynamic_cast<MappingType const&>(baseType).getKeyType(); + m_context << u256(0); + appendExpressionCopyToMemory(keyType, _indexAccess.getIndexExpression()); + solAssert(baseType.getSizeOnStack() == 1, + "Unexpected: Not exactly one stack slot taken by subscriptable expression."); + m_context << eth::Instruction::SWAP1; + appendTypeMoveToMemory(IntegerType(256)); + m_context << u256(0) << eth::Instruction::SHA3; + + m_currentLValue = LValue(m_context, LValue::LValueType::Storage, _indexAccess.getType()); m_currentLValue.retrieveValueIfLValueNotRequested(_indexAccess); return false; @@ -401,32 +563,44 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) Declaration const* declaration = _identifier.getReferencedDeclaration(); if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration)) { - if (magicVar->getType()->getCategory() == Type::Category::CONTRACT) // must be "this" - m_context << eth::Instruction::ADDRESS; - return; + if (magicVar->getType()->getCategory() == Type::Category::Contract) + // "this" or "super" + if (!dynamic_cast<ContractType const&>(*magicVar->getType()).isSuper()) + m_context << eth::Instruction::ADDRESS; } - if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) - { - m_context << m_context.getFunctionEntryLabel(*functionDef).pushTag(); - return; - } - if (dynamic_cast<VariableDeclaration const*>(declaration)) + else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) + m_context << m_context.getVirtualFunctionEntryLabel(*functionDef).pushTag(); + else if (dynamic_cast<VariableDeclaration const*>(declaration)) { m_currentLValue.fromIdentifier(_identifier, *declaration); m_currentLValue.retrieveValueIfLValueNotRequested(_identifier); - return; } - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier type not expected in expression context.")); + else if (dynamic_cast<ContractDefinition const*>(declaration)) + { + // no-op + } + else if (dynamic_cast<EventDefinition const*>(declaration)) + { + // no-op + } + else if (dynamic_cast<EnumDefinition const*>(declaration)) + { + // no-op + } + else + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier type not expected in expression context.")); + } } void ExpressionCompiler::endVisit(Literal const& _literal) { switch (_literal.getType()->getCategory()) { - case Type::Category::INTEGER: - case Type::Category::BOOL: - case Type::Category::STRING: - m_context << _literal.getType()->literalValue(_literal); + case Type::Category::IntegerConstant: + case Type::Category::Bool: + case Type::Category::String: + m_context << _literal.getType()->literalValue(&_literal); break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Only integer, boolean and string literals implemented for now.")); @@ -435,12 +609,12 @@ void ExpressionCompiler::endVisit(Literal const& _literal) void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryOperation) { - Token::Value const op = _binaryOperation.getOperator(); - solAssert(op == Token::OR || op == Token::AND, ""); + Token::Value const c_op = _binaryOperation.getOperator(); + solAssert(c_op == Token::Or || c_op == Token::And, ""); _binaryOperation.getLeftExpression().accept(*this); m_context << eth::Instruction::DUP1; - if (op == Token::AND) + if (c_op == Token::And) m_context << eth::Instruction::ISZERO; eth::AssemblyItem endLabel = m_context.appendConditionalJump(); m_context << eth::Instruction::POP; @@ -450,32 +624,32 @@ void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryO void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type) { - if (_operator == Token::EQ || _operator == Token::NE) + if (_operator == Token::Equal || _operator == Token::NotEqual) { m_context << eth::Instruction::EQ; - if (_operator == Token::NE) + if (_operator == Token::NotEqual) m_context << eth::Instruction::ISZERO; } else { IntegerType const& type = dynamic_cast<IntegerType const&>(_type); - bool const isSigned = type.isSigned(); + bool const c_isSigned = type.isSigned(); switch (_operator) { - case Token::GTE: - m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT) + case Token::GreaterThanOrEqual: + m_context << (c_isSigned ? eth::Instruction::SLT : eth::Instruction::LT) << eth::Instruction::ISZERO; break; - case Token::LTE: - m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT) + case Token::LessThanOrEqual: + m_context << (c_isSigned ? eth::Instruction::SGT : eth::Instruction::GT) << eth::Instruction::ISZERO; break; - case Token::GT: - m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT); + case Token::GreaterThan: + m_context << (c_isSigned ? eth::Instruction::SGT : eth::Instruction::GT); break; - case Token::LT: - m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT); + case Token::LessThan: + m_context << (c_isSigned ? eth::Instruction::SLT : eth::Instruction::LT); break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown comparison operator.")); @@ -498,24 +672,27 @@ void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type) { IntegerType const& type = dynamic_cast<IntegerType const&>(_type); - bool const isSigned = type.isSigned(); + bool const c_isSigned = type.isSigned(); switch (_operator) { - case Token::ADD: + case Token::Add: m_context << eth::Instruction::ADD; break; - case Token::SUB: + case Token::Sub: m_context << eth::Instruction::SUB; break; - case Token::MUL: + case Token::Mul: m_context << eth::Instruction::MUL; break; - case Token::DIV: - m_context << (isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV); + case Token::Div: + m_context << (c_isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV); break; - case Token::MOD: - m_context << (isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD); + case Token::Mod: + m_context << (c_isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD); + break; + case Token::Exp: + m_context << eth::Instruction::EXP; break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown arithmetic operator.")); @@ -526,13 +703,13 @@ void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) { switch (_operator) { - case Token::BIT_OR: + case Token::BitOr: m_context << eth::Instruction::OR; break; - case Token::BIT_AND: + case Token::BitAnd: m_context << eth::Instruction::AND; break; - case Token::BIT_XOR: + case Token::BitXor: m_context << eth::Instruction::XOR; break; default: @@ -562,12 +739,82 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con if (_typeOnStack == _targetType && !_cleanupNeeded) return; - if (_typeOnStack.getCategory() == Type::Category::INTEGER) - appendHighBitsCleanup(dynamic_cast<IntegerType const&>(_typeOnStack)); - else if (_typeOnStack.getCategory() == Type::Category::STRING) + Type::Category stackTypeCategory = _typeOnStack.getCategory(); + Type::Category targetTypeCategory = _targetType.getCategory(); + + if (stackTypeCategory == Type::Category::String) + { + StaticStringType const& typeOnStack = dynamic_cast<StaticStringType const&>(_typeOnStack); + if (targetTypeCategory == Type::Category::Integer) + { + // conversion from string to hash. no need to clean the high bit + // only to shift right because of opposite alignment + IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType); + solAssert(targetIntegerType.isHash(), "Only conversion between String and Hash is allowed."); + solAssert(targetIntegerType.getNumBits() == typeOnStack.getNumBytes() * 8, "The size should be the same."); + m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + } + else + { + // clear lower-order bytes for conversion to shorter strings - we always clean + solAssert(targetTypeCategory == Type::Category::String, "Invalid type conversion requested."); + StaticStringType const& targetType = dynamic_cast<StaticStringType const&>(_targetType); + if (targetType.getNumBytes() < typeOnStack.getNumBytes()) + { + if (targetType.getNumBytes() == 0) + m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; + else + m_context << (u256(1) << (256 - targetType.getNumBytes() * 8)) + << eth::Instruction::DUP1 << eth::Instruction::SWAP2 + << eth::Instruction::DIV << eth::Instruction::MUL; + } + } + } + else if (stackTypeCategory == Type::Category::Enum) + solAssert(targetTypeCategory == Type::Category::Integer || + targetTypeCategory == Type::Category::Enum, ""); + else if (stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::Contract || + stackTypeCategory == Type::Category::IntegerConstant) { - // nothing to do, strings are high-order-bit-aligned - //@todo clear lower-order bytes if we allow explicit conversion to shorter strings + if (targetTypeCategory == Type::Category::String && stackTypeCategory == Type::Category::Integer) + { + // conversion from hash to string. no need to clean the high bit + // only to shift left because of opposite alignment + StaticStringType const& targetStringType = dynamic_cast<StaticStringType const&>(_targetType); + IntegerType const& typeOnStack = dynamic_cast<IntegerType const&>(_typeOnStack); + solAssert(typeOnStack.isHash(), "Only conversion between String and Hash is allowed."); + solAssert(typeOnStack.getNumBits() == targetStringType.getNumBytes() * 8, "The size should be the same."); + m_context << (u256(1) << (256 - typeOnStack.getNumBits())) << eth::Instruction::MUL; + } + else if (targetTypeCategory == Type::Category::Enum) + // just clean + appendTypeConversion(_typeOnStack, *_typeOnStack.getRealType(), true); + else + { + solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); + IntegerType addressType(0, IntegerType::Modifier::Address); + IntegerType const& targetType = targetTypeCategory == Type::Category::Integer + ? dynamic_cast<IntegerType const&>(_targetType) : addressType; + if (stackTypeCategory == Type::Category::IntegerConstant) + { + IntegerConstantType const& constType = dynamic_cast<IntegerConstantType const&>(_typeOnStack); + // We know that the stack is clean, we only have to clean for a narrowing conversion + // where cleanup is forced. + if (targetType.getNumBits() < constType.getIntegerType()->getNumBits() && _cleanupNeeded) + appendHighBitsCleanup(targetType); + } + else + { + IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer + ? dynamic_cast<IntegerType const&>(_typeOnStack) : addressType; + // Widening: clean up according to source type width + // Non-widening and force: clean up according to target type bits + if (targetType.getNumBits() > typeOnStack.getNumBits()) + appendHighBitsCleanup(typeOnStack); + else if (_cleanupNeeded) + appendHighBitsCleanup(targetType); + } + } } else if (_typeOnStack != _targetType) // All other types should not be convertible to non-equal types. @@ -586,181 +833,415 @@ void ExpressionCompiler::appendHighBitsCleanup(IntegerType const& _typeOnStack) void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functionType, vector<ASTPointer<Expression const>> const& _arguments, - FunctionCallOptions const& _options) + bool bare) { - solAssert(_arguments.size() == _functionType.getParameterTypes().size(), ""); + solAssert(_functionType.takesArbitraryParameters() || + _arguments.size() == _functionType.getParameterTypes().size(), ""); + + // Assumed stack content here: + // <stack top> + // value [if _functionType.valueSet()] + // gas [if _functionType.gasSet()] + // function identifier [unless bare] + // contract address + + unsigned gasValueSize = (_functionType.gasSet() ? 1 : 0) + (_functionType.valueSet() ? 1 : 0); + + unsigned contractStackPos = m_context.currentToBaseStackOffset(1 + gasValueSize + (bare ? 0 : 1)); + unsigned gasStackPos = m_context.currentToBaseStackOffset(gasValueSize); + unsigned valueStackPos = m_context.currentToBaseStackOffset(1); - unsigned dataOffset = _options.bare ? 0 : 1; // reserve one byte for the function index - for (unsigned i = 0; i < _arguments.size(); ++i) - { - _arguments[i]->accept(*this); - Type const& type = *_functionType.getParameterTypes()[i]; - appendTypeConversion(*_arguments[i]->getType(), type); - unsigned const numBytes = _options.packDensely ? type.getCalldataEncodedSize() : 32; - if (numBytes == 0 || numBytes > 32) - BOOST_THROW_EXCEPTION(CompilerError() - << errinfo_sourceLocation(_arguments[i]->getLocation()) - << errinfo_comment("Type " + type.toString() + " not yet supported.")); - bool const leftAligned = type.getCategory() == Type::Category::STRING; - CompilerUtils(m_context).storeInMemory(dataOffset, numBytes, leftAligned); - dataOffset += numBytes; - } //@todo only return the first return value for now Type const* firstType = _functionType.getReturnParameterTypes().empty() ? nullptr : _functionType.getReturnParameterTypes().front().get(); - unsigned retSize = firstType ? firstType->getCalldataEncodedSize() : 0; - if (!_options.packDensely && retSize > 0) - retSize = 32; - // CALL arguments: outSize, outOff, inSize, inOff, value, addr, gas (stack top) - m_context << u256(retSize) << u256(0) << u256(dataOffset) << u256(0); - if (_options.obtainValue) - _options.obtainValue(); + unsigned retSize = firstType ? CompilerUtils::getPaddedSize(firstType->getCalldataEncodedSize()) : 0; + m_context << u256(retSize) << u256(0); + + if (bare) + m_context << u256(0); + else + { + // copy function identifier + m_context << eth::dupInstruction(gasValueSize + 3); + CompilerUtils(m_context).storeInMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8)); + m_context << u256(CompilerUtils::dataStartOffset); + } + + // For bare call, activate "4 byte pad exception": If the first argument has exactly 4 bytes, + // do not pad it to 32 bytes. + appendArgumentsCopyToMemory(_arguments, _functionType.getParameterTypes(), + _functionType.padArguments(), bare); + + // CALL arguments: outSize, outOff, inSize, (already present up to here) + // inOff, value, addr, gas (stack top) + m_context << u256(0); + if (_functionType.valueSet()) + m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos)); else m_context << u256(0); - _options.obtainAddress(); - if (!_options.bare) - m_context << u256(0) << eth::Instruction::MSTORE8; - m_context << u256(25) << eth::Instruction::GAS << eth::Instruction::SUB - << eth::Instruction::CALL + m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(contractStackPos)); + + if (_functionType.gasSet()) + m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos)); + else + // send all gas except for the 21 needed to execute "SUB" and "CALL" + m_context << u256(21) << eth::Instruction::GAS << eth::Instruction::SUB; + m_context << eth::Instruction::CALL << eth::Instruction::POP; // @todo do not ignore failure indicator - if (retSize > 0) + if (_functionType.valueSet()) + m_context << eth::Instruction::POP; + if (_functionType.gasSet()) + m_context << eth::Instruction::POP; + if (!bare) + m_context << eth::Instruction::POP; + m_context << eth::Instruction::POP; // pop contract address + + if (firstType) + CompilerUtils(m_context).loadFromMemory(0, *firstType, false, true); +} + +void ExpressionCompiler::appendArgumentsCopyToMemory(vector<ASTPointer<Expression const>> const& _arguments, + TypePointers const& _types, + bool _padToWordBoundaries, + bool _padExceptionIfFourBytes) +{ + solAssert(_types.empty() || _types.size() == _arguments.size(), ""); + for (size_t i = 0; i < _arguments.size(); ++i) { - bool const leftAligned = firstType->getCategory() == Type::Category::STRING; - CompilerUtils(m_context).loadFromMemory(0, retSize, leftAligned); + _arguments[i]->accept(*this); + TypePointer const& expectedType = _types.empty() ? _arguments[i]->getType()->getRealType() : _types[i]; + appendTypeConversion(*_arguments[i]->getType(), *expectedType, true); + bool pad = _padToWordBoundaries; + // Do not pad if the first argument has exactly four bytes + if (i == 0 && pad && _padExceptionIfFourBytes && expectedType->getCalldataEncodedSize() == 4) + pad = false; + appendTypeMoveToMemory(*expectedType, pad); } } -ExpressionCompiler::LValue::LValue(CompilerContext& _compilerContext, LValueType _type, Type const& _dataType, - unsigned _baseStackOffset): - m_context(&_compilerContext), m_type(_type), m_baseStackOffset(_baseStackOffset), - m_stackSize(_dataType.getSizeOnStack()) +void ExpressionCompiler::appendTypeMoveToMemory(Type const& _type, bool _padToWordBoundaries) +{ + CompilerUtils(m_context).storeInMemoryDynamic(_type, _padToWordBoundaries); +} + +void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression) { + _expression.accept(*this); + appendTypeConversion(*_expression.getType(), _expectedType, true); + appendTypeMoveToMemory(_expectedType); } -void ExpressionCompiler::LValue::retrieveValue(Expression const& _expression, bool _remove) const +void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl) +{ + FunctionType accessorType(_varDecl); + + unsigned length = 0; + TypePointers const& paramTypes = accessorType.getParameterTypes(); + // move arguments to memory + for (TypePointer const& paramType: boost::adaptors::reverse(paramTypes)) + length += CompilerUtils(m_context).storeInMemory(length, *paramType, true); + + // retrieve the position of the variable + m_context << m_context.getStorageLocationOfVariable(_varDecl); + TypePointer returnType = _varDecl.getType(); + + for (TypePointer const& paramType: paramTypes) + { + // move offset to memory + CompilerUtils(m_context).storeInMemory(length); + unsigned argLen = CompilerUtils::getPaddedSize(paramType->getCalldataEncodedSize()); + length -= argLen; + m_context << u256(argLen + 32) << u256(length) << eth::Instruction::SHA3; + + returnType = dynamic_cast<MappingType const&>(*returnType).getValueType(); + } + + unsigned retSizeOnStack = 0; + solAssert(accessorType.getReturnParameterTypes().size() >= 1, ""); + if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get())) + { + auto const& names = accessorType.getReturnParameterNames(); + auto const& types = accessorType.getReturnParameterTypes(); + // struct + for (size_t i = 0; i < names.size(); ++i) + { + m_context << eth::Instruction::DUP1 + << structType->getStorageOffsetOfMember(names[i]) + << eth::Instruction::ADD; + m_currentLValue = LValue(m_context, LValue::LValueType::Storage, types[i]); + m_currentLValue.retrieveValue(Location(), true); + solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented."); + m_context << eth::Instruction::SWAP1; + retSizeOnStack += types[i]->getSizeOnStack(); + } + m_context << eth::Instruction::POP; + } + else + { + // simple value + solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); + m_currentLValue = LValue(m_context, LValue::LValueType::Storage, returnType); + m_currentLValue.retrieveValue(Location(), true); + retSizeOnStack = returnType->getSizeOnStack(); + } + solAssert(retSizeOnStack <= 15, "Stack too deep."); + m_context << eth::dupInstruction(retSizeOnStack + 1) << eth::Instruction::JUMP; +} + +ExpressionCompiler::LValue::LValue(CompilerContext& _compilerContext, LValueType _type, + TypePointer const& _dataType, unsigned _baseStackOffset): + m_context(&_compilerContext), m_type(_type), m_dataType(_dataType), + m_baseStackOffset(_baseStackOffset) +{ + //@todo change the type cast for arrays + solAssert(m_dataType->getStorageSize() <= numeric_limits<unsigned>::max(), + "The storage size of " + m_dataType->toString() + " should fit in unsigned"); + if (m_type == LValueType::Storage) + m_size = unsigned(m_dataType->getStorageSize()); + else + m_size = unsigned(m_dataType->getSizeOnStack()); +} + +void ExpressionCompiler::LValue::fromIdentifier(Identifier const& _identifier, Declaration const& _declaration) +{ + if (m_context->isLocalVariable(&_declaration)) + { + m_type = LValueType::Stack; + m_dataType = _identifier.getType(); + m_size = m_dataType->getSizeOnStack(); + m_baseStackOffset = m_context->getBaseStackOffsetOfVariable(_declaration); + } + else if (m_context->isStateVariable(&_declaration)) + { + *m_context << m_context->getStorageLocationOfVariable(_declaration); + m_type = LValueType::Storage; + m_dataType = _identifier.getType(); + solAssert(m_dataType->getStorageSize() <= numeric_limits<unsigned>::max(), + "The storage size of " + m_dataType->toString() + " should fit in an unsigned"); + m_size = unsigned(m_dataType->getStorageSize()); } + else + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_identifier.getLocation()) + << errinfo_comment("Identifier type not supported or identifier not found.")); +} + +void ExpressionCompiler::LValue::retrieveValue(Location const& _location, bool _remove) const { switch (m_type) { - case STACK: + case LValueType::Stack: { unsigned stackPos = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)); if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); - for (unsigned i = 0; i < m_stackSize; ++i) + for (unsigned i = 0; i < m_size; ++i) *m_context << eth::dupInstruction(stackPos + 1); break; } - case STORAGE: - if (!_expression.getType()->isValueType()) - break; // no distinction between value and reference for non-value types - if (!_remove) - *m_context << eth::Instruction::DUP1; - if (m_stackSize == 1) - *m_context << eth::Instruction::SLOAD; - else - for (unsigned i = 0; i < m_stackSize; ++i) - { - *m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1; - if (i + 1 < m_stackSize) - *m_context << u256(1) << eth::Instruction::ADD; - else - *m_context << eth::Instruction::POP; - } + case LValueType::Storage: + retrieveValueFromStorage(_remove); break; - case MEMORY: - if (!_expression.getType()->isValueType()) + case LValueType::Memory: + if (!m_dataType->isValueType()) break; // no distinction between value and reference for non-value types - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Location type not yet implemented.")); break; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Unsupported location type.")); break; } } -void ExpressionCompiler::LValue::storeValue(Expression const& _expression, bool _move) const +void ExpressionCompiler::LValue::retrieveValueFromStorage(bool _remove) const +{ + if (!m_dataType->isValueType()) + return; // no distinction between value and reference for non-value types + if (!_remove) + *m_context << eth::Instruction::DUP1; + if (m_size == 1) + *m_context << eth::Instruction::SLOAD; + else + for (unsigned i = 0; i < m_size; ++i) + { + *m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1; + if (i + 1 < m_size) + *m_context << u256(1) << eth::Instruction::ADD; + else + *m_context << eth::Instruction::POP; + } +} + +void ExpressionCompiler::LValue::storeValue(Type const& _sourceType, Location const& _location, bool _move) const { switch (m_type) { - case STACK: + case LValueType::Stack: { - unsigned stackDiff = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)) - m_stackSize + 1; + unsigned stackDiff = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)) - m_size + 1; if (stackDiff > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); else if (stackDiff > 0) - for (unsigned i = 0; i < m_stackSize; ++i) + for (unsigned i = 0; i < m_size; ++i) *m_context << eth::swapInstruction(stackDiff) << eth::Instruction::POP; if (!_move) - retrieveValue(_expression); + retrieveValue(_location); break; } - case LValue::STORAGE: - if (!_expression.getType()->isValueType()) - break; // no distinction between value and reference for non-value types - // stack layout: value value ... value ref - if (!_move) // copy values + case LValueType::Storage: + // stack layout: value value ... value target_ref + if (m_dataType->isValueType()) { - if (m_stackSize + 1 > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) - << errinfo_comment("Stack too deep.")); - for (unsigned i = 0; i < m_stackSize; ++i) - *m_context << eth::dupInstruction(m_stackSize + 1) << eth::Instruction::SWAP1; + if (!_move) // copy values + { + if (m_size + 1 > 16) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) + << errinfo_comment("Stack too deep.")); + for (unsigned i = 0; i < m_size; ++i) + *m_context << eth::dupInstruction(m_size + 1) << eth::Instruction::SWAP1; + } + if (m_size > 0) // store high index value first + *m_context << u256(m_size - 1) << eth::Instruction::ADD; + for (unsigned i = 0; i < m_size; ++i) + { + if (i + 1 >= m_size) + *m_context << eth::Instruction::SSTORE; + else + // stack here: value value ... value value (target_ref+offset) + *m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 + << eth::Instruction::SSTORE + << u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB; + } } - if (m_stackSize > 0) // store high index value first - *m_context << u256(m_stackSize - 1) << eth::Instruction::ADD; - for (unsigned i = 0; i < m_stackSize; ++i) + else { - if (i + 1 >= m_stackSize) - *m_context << eth::Instruction::SSTORE; + solAssert(_sourceType.getCategory() == m_dataType->getCategory(), ""); + if (m_dataType->getCategory() == Type::Category::ByteArray) + { + CompilerUtils(*m_context).copyByteArrayToStorage( + dynamic_cast<ByteArrayType const&>(*m_dataType), + dynamic_cast<ByteArrayType const&>(_sourceType)); + if (_move) + *m_context << eth::Instruction::POP; + } + else if (m_dataType->getCategory() == Type::Category::Struct) + { + // stack layout: source_ref target_ref + auto const& structType = dynamic_cast<StructType const&>(*m_dataType); + solAssert(structType == _sourceType, "Struct assignment with conversion."); + for (auto const& member: structType.getMembers()) + { + // assign each member that is not a mapping + TypePointer const& memberType = member.second; + if (memberType->getCategory() == Type::Category::Mapping) + continue; + *m_context << structType.getStorageOffsetOfMember(member.first) + << eth::Instruction::DUP3 << eth::Instruction::DUP2 + << eth::Instruction::ADD; + // stack: source_ref target_ref member_offset source_member_ref + LValue rightHandSide(*m_context, LValueType::Storage, memberType); + rightHandSide.retrieveValue(_location, true); + // stack: source_ref target_ref member_offset source_value... + *m_context << eth::dupInstruction(2 + memberType->getSizeOnStack()) + << eth::dupInstruction(2 + memberType->getSizeOnStack()) + << eth::Instruction::ADD; + // stack: source_ref target_ref member_offset source_value... target_member_ref + LValue memberLValue(*m_context, LValueType::Storage, memberType); + memberLValue.storeValue(*memberType, _location, true); + *m_context << eth::Instruction::POP; + } + if (_move) + *m_context << eth::Instruction::POP; + else + *m_context << eth::Instruction::SWAP1; + *m_context << eth::Instruction::POP; + } else - // v v ... v v r+x - *m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 - << eth::Instruction::SSTORE - << u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB; + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) + << errinfo_comment("Invalid non-value type for assignment.")); } break; - case LValue::MEMORY: - if (!_expression.getType()->isValueType()) + case LValueType::Memory: + if (!m_dataType->isValueType()) break; // no distinction between value and reference for non-value types - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Location type not yet implemented.")); break; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Unsupported location type.")); break; } } -void ExpressionCompiler::LValue::retrieveValueIfLValueNotRequested(Expression const& _expression) +void ExpressionCompiler::LValue::setToZero(Location const& _location) const { - if (!_expression.lvalueRequested()) + switch (m_type) { - retrieveValue(_expression, true); - reset(); + case LValueType::Stack: + { + unsigned stackDiff = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)); + if (stackDiff > 16) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) + << errinfo_comment("Stack too deep.")); + solAssert(stackDiff >= m_size - 1, ""); + for (unsigned i = 0; i < m_size; ++i) + *m_context << u256(0) << eth::swapInstruction(stackDiff + 1 - i) + << eth::Instruction::POP; + break; + } + case LValueType::Storage: + if (m_dataType->getCategory() == Type::Category::ByteArray) + CompilerUtils(*m_context).clearByteArray(dynamic_cast<ByteArrayType const&>(*m_dataType)); + else if (m_dataType->getCategory() == Type::Category::Struct) + { + // stack layout: ref + auto const& structType = dynamic_cast<StructType const&>(*m_dataType); + for (auto const& member: structType.getMembers()) + { + // zero each member that is not a mapping + TypePointer const& memberType = member.second; + if (memberType->getCategory() == Type::Category::Mapping) + continue; + *m_context << structType.getStorageOffsetOfMember(member.first) + << eth::Instruction::DUP2 << eth::Instruction::ADD; + LValue memberValue(*m_context, LValueType::Storage, memberType); + memberValue.setToZero(); + } + *m_context << eth::Instruction::POP; + } + else + { + if (m_size == 0) + *m_context << eth::Instruction::POP; + for (unsigned i = 0; i < m_size; ++i) + if (i + 1 >= m_size) + *m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + else + *m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE + << u256(1) << eth::Instruction::ADD; + } + break; + case LValueType::Memory: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) + << errinfo_comment("Location type not yet implemented.")); + break; + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) + << errinfo_comment("Unsupported location type.")); + break; } } -void ExpressionCompiler::LValue::fromIdentifier(Identifier const& _identifier, Declaration const& _declaration) +void ExpressionCompiler::LValue::retrieveValueIfLValueNotRequested(Expression const& _expression) { - m_stackSize = _identifier.getType()->getSizeOnStack(); - if (m_context->isLocalVariable(&_declaration)) - { - m_type = STACK; - m_baseStackOffset = m_context->getBaseStackOffsetOfVariable(_declaration); - } - else if (m_context->isStateVariable(&_declaration)) + if (!_expression.lvalueRequested()) { - m_type = STORAGE; - *m_context << m_context->getStorageLocationOfVariable(_declaration); + retrieveValue(_expression.getLocation(), true); + reset(); } - else - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_identifier.getLocation()) - << errinfo_comment("Identifier type not supported or identifier not found.")); } } diff --git a/ExpressionCompiler.h b/ExpressionCompiler.h index 67b16aac..471d8186 100644 --- a/ExpressionCompiler.h +++ b/ExpressionCompiler.h @@ -16,13 +16,16 @@ */ /** * @author Christian <c@ethdev.com> + * @author Gav Wood <g@ethdev.com> * @date 2014 * Solidity AST to EVM bytecode compiler for expressions. */ #include <functional> +#include <memory> #include <boost/noncopyable.hpp> #include <libdevcore/Common.h> +#include <libsolidity/BaseTypes.h> #include <libsolidity/ASTVisitor.h> namespace dev { @@ -36,6 +39,7 @@ namespace solidity { class CompilerContext; class Type; class IntegerType; +class ByteArrayType; class StaticStringType; /** @@ -50,14 +54,17 @@ public: static void compileExpression(CompilerContext& _context, Expression const& _expression, bool _optimize = false); /// Appends code to remove dirty higher order bits in case of an implicit promotion to a wider type. - static void appendTypeConversion(CompilerContext& _context, Type const& _typeOnStack, Type const& _targetType); + static void appendTypeConversion(CompilerContext& _context, Type const& _typeOnStack, + Type const& _targetType, bool _cleanupNeeded = false); + /// Appends code for a State Variable accessor function + static void appendStateVariableAccessor(CompilerContext& _context, VariableDeclaration const& _varDecl, bool _optimize = false); private: explicit ExpressionCompiler(CompilerContext& _compilerContext, bool _optimize = false): m_optimize(_optimize), m_context(_compilerContext), m_currentLValue(m_context) {} virtual bool visit(Assignment const& _assignment) override; - virtual void endVisit(UnaryOperation const& _unaryOperation) override; + virtual bool visit(UnaryOperation const& _unaryOperation) override; virtual bool visit(BinaryOperation const& _binaryOperation) override; virtual bool visit(FunctionCall const& _functionCall) override; virtual bool visit(NewExpression const& _newExpression) override; @@ -85,24 +92,24 @@ private: //// Appends code that cleans higher-order bits for integer types. void appendHighBitsCleanup(IntegerType const& _typeOnStack); - /// Additional options used in appendExternalFunctionCall. - struct FunctionCallOptions - { - FunctionCallOptions() {} - /// Invoked to copy the address to the stack - std::function<void()> obtainAddress; - /// Invoked to copy the ethe value to the stack (if not specified, value is 0). - std::function<void()> obtainValue; - /// If true, do not prepend function index to call data - bool bare = false; - /// If false, use calling convention that all arguments and return values are packed as - /// 32 byte values with padding. - bool packDensely = true; - }; - /// Appends code to call a function of the given type with the given arguments. void appendExternalFunctionCall(FunctionType const& _functionType, std::vector<ASTPointer<Expression const>> const& _arguments, - FunctionCallOptions const& _options = FunctionCallOptions()); + bool bare = false); + /// Appends code that evaluates the given arguments and moves the result to memory. The memory offset is + /// expected to be on the stack and is updated by this call. + void appendArgumentsCopyToMemory(std::vector<ASTPointer<Expression const>> const& _arguments, + TypePointers const& _types = {}, + bool _padToWordBoundaries = true, + bool _padExceptionIfFourBytes = false); + /// Appends code that moves a stack element of the given type to memory. The memory offset is + /// expected below the stack element and is updated by this call. + void appendTypeMoveToMemory(Type const& _type, bool _padToWordBoundaries = true); + /// Appends code that evaluates a single expression and moves the result to memory. The memory offset is + /// expected to be on the stack and is updated by this call. + void appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression); + + /// Appends code for a State Variable accessor function + void appendStateVariableAccessor(VariableDeclaration const& _varDecl); /** * Helper class to store and retrieve lvalues to and from various locations. @@ -112,46 +119,55 @@ private: class LValue { public: - enum LValueType { NONE, STACK, MEMORY, STORAGE }; + enum class LValueType { None, Stack, Memory, Storage }; explicit LValue(CompilerContext& _compilerContext): m_context(&_compilerContext) { reset(); } - LValue(CompilerContext& _compilerContext, LValueType _type, Type const& _dataType, unsigned _baseStackOffset = 0); + LValue(CompilerContext& _compilerContext, LValueType _type, + std::shared_ptr<Type const> const& _dataType, unsigned _baseStackOffset = 0); /// Set type according to the declaration and retrieve the reference. /// @a _expression is the current expression void fromIdentifier(Identifier const& _identifier, Declaration const& _declaration); - void reset() { m_type = NONE; m_baseStackOffset = 0; } + void reset() { m_type = LValueType::None; m_dataType.reset(); m_baseStackOffset = 0; m_size = 0; } - bool isValid() const { return m_type != NONE; } - bool isInOnStack() const { return m_type == STACK; } - bool isInMemory() const { return m_type == MEMORY; } - bool isInStorage() const { return m_type == STORAGE; } + bool isValid() const { return m_type != LValueType::None; } + bool isInOnStack() const { return m_type == LValueType::Stack; } + bool isInMemory() const { return m_type == LValueType::Memory; } + bool isInStorage() const { return m_type == LValueType::Storage; } /// @returns true if this lvalue reference type occupies a slot on the stack. - bool storesReferenceOnStack() const { return m_type == STORAGE || m_type == MEMORY; } + bool storesReferenceOnStack() const { return m_type == LValueType::Storage || m_type == LValueType::Memory; } /// Copies the value of the current lvalue to the top of the stack and, if @a _remove is true, /// also removes the reference from the stack (note that is does not reset the type to @a NONE). - /// @a _expression is the current expression, used for error reporting. - void retrieveValue(Expression const& _expression, bool _remove = false) const; - /// Stores a value (from the stack directly beneath the reference, which is assumed to - /// be on the top of the stack, if any) in the lvalue and removes the reference. - /// Also removes the stored value from the stack if @a _move is - /// true. @a _expression is the current expression, used for error reporting. - void storeValue(Expression const& _expression, bool _move = false) const; - + /// @a _location source location of the current expression, used for error reporting. + void retrieveValue(Location const& _location, bool _remove = false) const; + /// Moves a value from the stack to the lvalue. Removes the value if @a _move is true. + /// @a _location is the source location of the expression that caused this operation. + /// Stack pre: value [lvalue_ref] + /// Stack post if !_move: value_of(lvalue_ref) + void storeValue(Type const& _sourceType, Location const& _location = Location(), bool _move = false) const; + /// Stores zero in the lvalue. + /// @a _location is the source location of the requested operation + void setToZero(Location const& _location = Location()) const; /// Convenience function to convert the stored reference to a value and reset type to NONE if /// the reference was not requested by @a _expression. void retrieveValueIfLValueNotRequested(Expression const& _expression); private: + /// Convenience function to retrieve Value from Storage. Specific version of @ref retrieveValue + void retrieveValueFromStorage(bool _remove = false) const; + /// Copies from a byte array to a byte array in storage, both references on the stack. + void copyByteArrayToStorage(ByteArrayType const& _targetType, ByteArrayType const& _sourceType) const; + CompilerContext* m_context; - LValueType m_type; + LValueType m_type = LValueType::None; + std::shared_ptr<Type const> m_dataType; /// If m_type is STACK, this is base stack offset (@see /// CompilerContext::getBaseStackOffsetOfVariable) of a local variable. - unsigned m_baseStackOffset; - /// Size of the value of this lvalue on the stack. - unsigned m_stackSize; + unsigned m_baseStackOffset = 0; + /// Size of the value of this lvalue on the stack or the storage. + unsigned m_size = 0; }; bool m_optimize; diff --git a/GlobalContext.cpp b/GlobalContext.cpp index f4805b1f..60de5105 100644 --- a/GlobalContext.cpp +++ b/GlobalContext.cpp @@ -16,6 +16,7 @@ */ /** * @author Christian <c@ethdev.com> + * @author Gav Wood <g@ethdev.com> * @date 2014 * Container of the (implicit and explicit) global objects. */ @@ -33,33 +34,29 @@ namespace solidity { GlobalContext::GlobalContext(): -m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::BLOCK)), - make_shared<MagicVariableDeclaration>("msg", make_shared<MagicType>(MagicType::Kind::MSG)), - make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::TX)), - make_shared<MagicVariableDeclaration>("suicide", - make_shared<FunctionType>(TypePointers({std::make_shared<IntegerType>(0, - IntegerType::Modifier::ADDRESS)}), - TypePointers(), - FunctionType::Location::SUICIDE)), - make_shared<MagicVariableDeclaration>("sha3", - make_shared<FunctionType>(TypePointers({std::make_shared<IntegerType>(256, IntegerType::Modifier::HASH)}), - TypePointers({std::make_shared<IntegerType>(256, IntegerType::Modifier::HASH)}), - FunctionType::Location::SHA3)), - make_shared<MagicVariableDeclaration>("sha256", - make_shared<FunctionType>(TypePointers({std::make_shared<IntegerType>(256, IntegerType::Modifier::HASH)}), - TypePointers({std::make_shared<IntegerType>(256, IntegerType::Modifier::HASH)}), - FunctionType::Location::SHA256)), - make_shared<MagicVariableDeclaration>("ecrecover", - make_shared<FunctionType>(TypePointers({std::make_shared<IntegerType>(256, IntegerType::Modifier::HASH), - std::make_shared<IntegerType>(8, IntegerType::Modifier::HASH), - std::make_shared<IntegerType>(256, IntegerType::Modifier::HASH), - std::make_shared<IntegerType>(256, IntegerType::Modifier::HASH)}), - TypePointers({std::make_shared<IntegerType>(0, IntegerType::Modifier::ADDRESS)}), - FunctionType::Location::ECRECOVER)), - make_shared<MagicVariableDeclaration>("ripemd160", - make_shared<FunctionType>(TypePointers({std::make_shared<IntegerType>(256, IntegerType::Modifier::HASH)}), - TypePointers({std::make_shared<IntegerType>(160, IntegerType::Modifier::HASH)}), - FunctionType::Location::RIPEMD160))}) +m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block)), + make_shared<MagicVariableDeclaration>("msg", make_shared<MagicType>(MagicType::Kind::Message)), + make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)), + make_shared<MagicVariableDeclaration>("suicide", + make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Suicide)), + make_shared<MagicVariableDeclaration>("sha3", + make_shared<FunctionType>(strings(), strings{"hash"}, FunctionType::Location::SHA3, true)), + make_shared<MagicVariableDeclaration>("log0", + make_shared<FunctionType>(strings{"hash"},strings{}, FunctionType::Location::Log0)), + make_shared<MagicVariableDeclaration>("log1", + make_shared<FunctionType>(strings{"hash", "hash"},strings{}, FunctionType::Location::Log1)), + make_shared<MagicVariableDeclaration>("log2", + make_shared<FunctionType>(strings{"hash", "hash", "hash"},strings{}, FunctionType::Location::Log2)), + make_shared<MagicVariableDeclaration>("log3", + make_shared<FunctionType>(strings{"hash", "hash", "hash", "hash"},strings{}, FunctionType::Location::Log3)), + make_shared<MagicVariableDeclaration>("log4", + make_shared<FunctionType>(strings{"hash", "hash", "hash", "hash", "hash"},strings{}, FunctionType::Location::Log4)), + make_shared<MagicVariableDeclaration>("sha256", + make_shared<FunctionType>(strings(), strings{"hash"}, FunctionType::Location::SHA256, true)), + make_shared<MagicVariableDeclaration>("ecrecover", + make_shared<FunctionType>(strings{"hash", "hash8", "hash", "hash"}, strings{"address"}, FunctionType::Location::ECRecover)), + make_shared<MagicVariableDeclaration>("ripemd160", + make_shared<FunctionType>(strings(), strings{"hash160"}, FunctionType::Location::RIPEMD160, true))}) { } @@ -71,7 +68,7 @@ void GlobalContext::setCurrentContract(ContractDefinition const& _contract) vector<Declaration const*> GlobalContext::getDeclarations() const { vector<Declaration const*> declarations; - declarations.reserve(m_magicVariables.size() + 1); + declarations.reserve(m_magicVariables.size()); for (ASTPointer<Declaration const> const& variable: m_magicVariables) declarations.push_back(variable.get()); return declarations; @@ -86,14 +83,12 @@ MagicVariableDeclaration const* GlobalContext::getCurrentThis() const } -vector<MagicVariableDeclaration const*> GlobalContext::getMagicVariables() const +MagicVariableDeclaration const* GlobalContext::getCurrentSuper() const { - vector<MagicVariableDeclaration const*> declarations; - declarations.reserve(m_magicVariables.size() + 1); - for (ASTPointer<MagicVariableDeclaration const> const& variable: m_magicVariables) - declarations.push_back(variable.get()); - declarations.push_back(getCurrentThis()); - return declarations; + if (!m_superPointer[m_currentContract]) + m_superPointer[m_currentContract] = make_shared<MagicVariableDeclaration>( + "super", make_shared<ContractType>(*m_currentContract, true)); + return m_superPointer[m_currentContract].get(); } } diff --git a/GlobalContext.h b/GlobalContext.h index 50a21f70..f861c67d 100644 --- a/GlobalContext.h +++ b/GlobalContext.h @@ -48,16 +48,16 @@ public: GlobalContext(); void setCurrentContract(ContractDefinition const& _contract); MagicVariableDeclaration const* getCurrentThis() const; + MagicVariableDeclaration const* getCurrentSuper() const; - /// @returns all magic variables. - std::vector<MagicVariableDeclaration const*> getMagicVariables() const; /// @returns a vector of all implicit global declarations excluding "this". std::vector<Declaration const*> getDeclarations() const; private: std::vector<std::shared_ptr<MagicVariableDeclaration const>> m_magicVariables; - ContractDefinition const* m_currentContract; + ContractDefinition const* m_currentContract = nullptr; std::map<ContractDefinition const*, std::shared_ptr<MagicVariableDeclaration const>> mutable m_thisPointer; + std::map<ContractDefinition const*, std::shared_ptr<MagicVariableDeclaration const>> mutable m_superPointer; }; } diff --git a/InterfaceHandler.cpp b/InterfaceHandler.cpp index 224234cb..99a7db96 100644 --- a/InterfaceHandler.cpp +++ b/InterfaceHandler.cpp @@ -2,6 +2,7 @@ #include <libsolidity/InterfaceHandler.h> #include <libsolidity/AST.h> #include <libsolidity/CompilerStack.h> +using namespace std; namespace dev { @@ -12,7 +13,7 @@ namespace solidity InterfaceHandler::InterfaceHandler() { - m_lastTag = DocTagType::NONE; + m_lastTag = DocTagType::None; } std::unique_ptr<std::string> InterfaceHandler::getDocumentation(ContractDefinition const& _contractDef, @@ -20,12 +21,14 @@ std::unique_ptr<std::string> InterfaceHandler::getDocumentation(ContractDefiniti { switch(_type) { - case DocumentationType::NATSPEC_USER: + case DocumentationType::NatspecUser: return getUserDocumentation(_contractDef); - case DocumentationType::NATSPEC_DEV: + case DocumentationType::NatspecDev: return getDevDocumentation(_contractDef); - case DocumentationType::ABI_INTERFACE: + case DocumentationType::ABIInterface: return getABIInterface(_contractDef); + case DocumentationType::ABISolidityInterface: + return getABISolidityInterface(_contractDef); } BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown documentation type")); @@ -34,34 +37,79 @@ std::unique_ptr<std::string> InterfaceHandler::getDocumentation(ContractDefiniti std::unique_ptr<std::string> InterfaceHandler::getABIInterface(ContractDefinition const& _contractDef) { - Json::Value methods(Json::arrayValue); - - for (FunctionDefinition const* f: _contractDef.getInterfaceFunctions()) + Json::Value abi(Json::arrayValue); + for (auto const& it: _contractDef.getInterfaceFunctions()) { - Json::Value method; - Json::Value inputs(Json::arrayValue); - Json::Value outputs(Json::arrayValue); - - auto populateParameters = [](std::vector<ASTPointer<VariableDeclaration>> const& _vars) + auto populateParameters = [](vector<string> const& _paramNames, vector<string> const& _paramTypes) { Json::Value params(Json::arrayValue); - for (ASTPointer<VariableDeclaration> const& var: _vars) + solAssert(_paramNames.size() == _paramTypes.size(), "Names and types vector size does not match"); + for (unsigned i = 0; i < _paramNames.size(); ++i) { - Json::Value input; - input["name"] = var->getName(); - input["type"] = var->getType()->toString(); - params.append(input); + Json::Value param; + param["name"] = _paramNames[i]; + param["type"] = _paramTypes[i]; + params.append(param); } return params; }; - method["name"] = f->getName(); - method["constant"] = f->isDeclaredConst(); - method["inputs"] = populateParameters(f->getParameters()); - method["outputs"] = populateParameters(f->getReturnParameters()); - methods.append(method); + Json::Value method; + method["type"] = "function"; + method["name"] = it.second->getDeclaration().getName(); + method["constant"] = it.second->isConstant(); + method["inputs"] = populateParameters(it.second->getParameterNames(), + it.second->getParameterTypeNames()); + method["outputs"] = populateParameters(it.second->getReturnParameterNames(), + it.second->getReturnParameterTypeNames()); + abi.append(method); } - return std::unique_ptr<std::string>(new std::string(m_writer.write(methods))); + + for (auto const& it: _contractDef.getInterfaceEvents()) + { + Json::Value event; + event["type"] = "event"; + event["name"] = it->getName(); + Json::Value params(Json::arrayValue); + for (auto const& p: it->getParameters()) + { + Json::Value input; + input["name"] = p->getName(); + input["type"] = p->getType()->toString(); + input["indexed"] = p->isIndexed(); + params.append(input); + } + event["inputs"] = params; + abi.append(event); + } + return std::unique_ptr<std::string>(new std::string(m_writer.write(abi))); +} + +unique_ptr<string> InterfaceHandler::getABISolidityInterface(ContractDefinition const& _contractDef) +{ + string ret = "contract " + _contractDef.getName() + "{"; + for (auto const& it: _contractDef.getInterfaceFunctions()) + { + auto populateParameters = [](vector<string> const& _paramNames, + vector<string> const& _paramTypes) + { + string r = ""; + solAssert(_paramNames.size() == _paramTypes.size(), "Names and types vector size does not match"); + for (unsigned i = 0; i < _paramNames.size(); ++i) + r += (r.size() ? "," : "(") + _paramTypes[i] + " " + _paramNames[i]; + return r.size() ? r + ")" : "()"; + }; + ret += "function " + it.second->getDeclaration().getName() + + populateParameters(it.second->getParameterNames(), it.second->getParameterTypeNames()) + + (it.second->isConstant() ? "constant " : ""); + if (it.second->getReturnParameterTypes().size()) + ret += "returns" + populateParameters(it.second->getReturnParameterNames(), it.second->getReturnParameterTypeNames()); + else if (ret.back() == ' ') + ret.pop_back(); + ret += "{}"; + } + + return unique_ptr<string>(new string(ret + "}")); } std::unique_ptr<std::string> InterfaceHandler::getUserDocumentation(ContractDefinition const& _contractDef) @@ -69,18 +117,18 @@ std::unique_ptr<std::string> InterfaceHandler::getUserDocumentation(ContractDefi Json::Value doc; Json::Value methods(Json::objectValue); - for (FunctionDefinition const* f: _contractDef.getInterfaceFunctions()) + for (auto const& it: _contractDef.getInterfaceFunctions()) { Json::Value user; - auto strPtr = f->getDocumentation(); + auto strPtr = it.second->getDocumentation(); if (strPtr) { resetUser(); - parseDocString(*strPtr, CommentOwner::FUNCTION); + parseDocString(*strPtr, CommentOwner::Function); if (!m_notice.empty()) {// since @notice is the only user tag if missing function should not appear user["notice"] = Json::Value(m_notice); - methods[f->getName()] = user; + methods[it.second->getCanonicalSignature()] = user; } } } @@ -101,7 +149,7 @@ std::unique_ptr<std::string> InterfaceHandler::getDevDocumentation(ContractDefin { m_contractAuthor.clear(); m_title.clear(); - parseDocString(*contractDoc, CommentOwner::CONTRACT); + parseDocString(*contractDoc, CommentOwner::Contract); if (!m_contractAuthor.empty()) doc["author"] = m_contractAuthor; @@ -110,14 +158,14 @@ std::unique_ptr<std::string> InterfaceHandler::getDevDocumentation(ContractDefin doc["title"] = m_title; } - for (FunctionDefinition const* f: _contractDef.getInterfaceFunctions()) + for (auto const& it: _contractDef.getInterfaceFunctions()) { Json::Value method; - auto strPtr = f->getDocumentation(); + auto strPtr = it.second->getDocumentation(); if (strPtr) { resetDev(); - parseDocString(*strPtr, CommentOwner::FUNCTION); + parseDocString(*strPtr, CommentOwner::Function); if (!m_dev.empty()) method["details"] = Json::Value(m_dev); @@ -136,7 +184,7 @@ std::unique_ptr<std::string> InterfaceHandler::getDevDocumentation(ContractDefin method["return"] = m_return; if (!method.empty()) // add the function, only if we have any documentation to add - methods[f->getName()] = method; + methods[it.second->getCanonicalSignature()] = method; } } doc["methods"] = methods; @@ -194,7 +242,7 @@ std::string::const_iterator InterfaceHandler::parseDocTagParam(std::string::cons auto paramDesc = std::string(currPos, nlPos); m_params.push_back(std::make_pair(paramName, paramDesc)); - m_lastTag = DocTagType::PARAM; + m_lastTag = DocTagType::Param; return skipLineOrEOS(nlPos, _end); } @@ -223,28 +271,28 @@ std::string::const_iterator InterfaceHandler::parseDocTag(std::string::const_ite // LTODO: need to check for @(start of a tag) between here and the end of line // for all cases. Also somehow automate list of acceptable tags for each // language construct since current way does not scale well. - if (m_lastTag == DocTagType::NONE || _tag != "") + if (m_lastTag == DocTagType::None || _tag != "") { if (_tag == "dev") - return parseDocTagLine(_pos, _end, m_dev, DocTagType::DEV, false); + return parseDocTagLine(_pos, _end, m_dev, DocTagType::Dev, false); else if (_tag == "notice") - return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE, false); + return parseDocTagLine(_pos, _end, m_notice, DocTagType::Notice, false); else if (_tag == "return") - return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN, false); + return parseDocTagLine(_pos, _end, m_return, DocTagType::Return, false); else if (_tag == "author") { - if (_owner == CommentOwner::CONTRACT) - return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::AUTHOR, false); - else if (_owner == CommentOwner::FUNCTION) - return parseDocTagLine(_pos, _end, m_author, DocTagType::AUTHOR, false); + if (_owner == CommentOwner::Contract) + return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::Author, false); + else if (_owner == CommentOwner::Function) + return parseDocTagLine(_pos, _end, m_author, DocTagType::Author, false); else // LTODO: for now this else makes no sense but later comments will go to more language constructs BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@author tag is legal only for contracts")); } else if (_tag == "title") { - if (_owner == CommentOwner::CONTRACT) - return parseDocTagLine(_pos, _end, m_title, DocTagType::TITLE, false); + if (_owner == CommentOwner::Contract) + return parseDocTagLine(_pos, _end, m_title, DocTagType::Title, false); else // LTODO: Unknown tag, throw some form of warning and not just an exception BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@title tag is legal only for contracts")); @@ -265,27 +313,27 @@ std::string::const_iterator InterfaceHandler::appendDocTag(std::string::const_it { switch (m_lastTag) { - case DocTagType::DEV: - return parseDocTagLine(_pos, _end, m_dev, DocTagType::DEV, true); - case DocTagType::NOTICE: - return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE, true); - case DocTagType::RETURN: - return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN, true); - case DocTagType::AUTHOR: - if (_owner == CommentOwner::CONTRACT) - return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::AUTHOR, true); - else if (_owner == CommentOwner::FUNCTION) - return parseDocTagLine(_pos, _end, m_author, DocTagType::AUTHOR, true); + case DocTagType::Dev: + return parseDocTagLine(_pos, _end, m_dev, DocTagType::Dev, true); + case DocTagType::Notice: + return parseDocTagLine(_pos, _end, m_notice, DocTagType::Notice, true); + case DocTagType::Return: + return parseDocTagLine(_pos, _end, m_return, DocTagType::Return, true); + case DocTagType::Author: + if (_owner == CommentOwner::Contract) + return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::Author, true); + else if (_owner == CommentOwner::Function) + return parseDocTagLine(_pos, _end, m_author, DocTagType::Author, true); else // LTODO: Unknown tag, throw some form of warning and not just an exception BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@author tag in illegal comment")); - case DocTagType::TITLE: - if (_owner == CommentOwner::CONTRACT) - return parseDocTagLine(_pos, _end, m_title, DocTagType::TITLE, true); + case DocTagType::Title: + if (_owner == CommentOwner::Contract) + return parseDocTagLine(_pos, _end, m_title, DocTagType::Title, true); else // LTODO: Unknown tag, throw some form of warning and not just an exception BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@title tag in illegal comment")); - case DocTagType::PARAM: + case DocTagType::Param: return appendDocTagParam(_pos, _end); default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Internal: Illegal documentation tag type")); @@ -321,10 +369,21 @@ void InterfaceHandler::parseDocString(std::string const& _string, CommentOwner _ currPos = parseDocTag(tagNameEndPos + 1, end, std::string(tagPos + 1, tagNameEndPos), _owner); } - else if (m_lastTag != DocTagType::NONE) // continuation of the previous tag + else if (m_lastTag != DocTagType::None) // continuation of the previous tag currPos = appendDocTag(currPos, end, _owner); - else if (currPos != end) // skip the line if a newline was found + else if (currPos != end) + { + // if it begins without a tag then consider it as @notice + if (currPos == _string.begin()) + { + currPos = parseDocTag(currPos, end, "notice", CommentOwner::Function); + continue; + } + else if (nlPos == end) //end of text + return; + // else skip the line if a newline was found and we get here currPos = nlPos + 1; + } } } diff --git a/InterfaceHandler.h b/InterfaceHandler.h index c8399d71..6aa3f72d 100644 --- a/InterfaceHandler.h +++ b/InterfaceHandler.h @@ -28,7 +28,7 @@ #include <string> #include <memory> -#include <jsoncpp/json/json.h> +#include <json/json.h> namespace dev { @@ -41,19 +41,19 @@ enum class DocumentationType: uint8_t; enum class DocTagType: uint8_t { - NONE = 0, - DEV, - NOTICE, - PARAM, - RETURN, - AUTHOR, - TITLE + None = 0, + Dev, + Notice, + Param, + Return, + Author, + Title }; enum class CommentOwner { - CONTRACT, - FUNCTION + Contract, + Function }; class InterfaceHandler @@ -74,6 +74,7 @@ public: /// @return A unique pointer contained string with the json /// representation of the contract's ABI Interface std::unique_ptr<std::string> getABIInterface(ContractDefinition const& _contractDef); + std::unique_ptr<std::string> getABISolidityInterface(ContractDefinition const& _contractDef); /// Get the User documentation of the contract /// @param _contractDef The contract definition /// @return A unique pointer contained string with the json diff --git a/NameAndTypeResolver.cpp b/NameAndTypeResolver.cpp index 3774537d..e19b0bf9 100644 --- a/NameAndTypeResolver.cpp +++ b/NameAndTypeResolver.cpp @@ -31,7 +31,6 @@ namespace dev namespace solidity { - NameAndTypeResolver::NameAndTypeResolver(std::vector<Declaration const*> const& _globals) { for (Declaration const* declaration: _globals) @@ -46,18 +45,36 @@ void NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit) void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) { + m_currentScope = &m_scopes[nullptr]; + + for (ASTPointer<InheritanceSpecifier> const& baseContract: _contract.getBaseContracts()) + ReferencesResolver resolver(*baseContract, *this, &_contract, nullptr); + m_currentScope = &m_scopes[&_contract]; + + linearizeBaseContracts(_contract); + for (ContractDefinition const* base: _contract.getLinearizedBaseContracts()) + importInheritedScope(*base); + for (ASTPointer<StructDefinition> const& structDef: _contract.getDefinedStructs()) - ReferencesResolver resolver(*structDef, *this, nullptr); + ReferencesResolver resolver(*structDef, *this, &_contract, nullptr); + for (ASTPointer<EnumDefinition> const& enumDef: _contract.getDefinedEnums()) + ReferencesResolver resolver(*enumDef, *this, &_contract, nullptr); for (ASTPointer<VariableDeclaration> const& variable: _contract.getStateVariables()) - ReferencesResolver resolver(*variable, *this, nullptr); + ReferencesResolver resolver(*variable, *this, &_contract, nullptr); + for (ASTPointer<EventDefinition> const& event: _contract.getEvents()) + ReferencesResolver resolver(*event, *this, &_contract, nullptr); + for (ASTPointer<ModifierDefinition> const& modifier: _contract.getFunctionModifiers()) + { + m_currentScope = &m_scopes[modifier.get()]; + ReferencesResolver resolver(*modifier, *this, &_contract, nullptr); + } for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions()) { m_currentScope = &m_scopes[function.get()]; - ReferencesResolver referencesResolver(*function, *this, + ReferencesResolver referencesResolver(*function, *this, &_contract, function->getReturnParameterList().get()); } - m_currentScope = &m_scopes[nullptr]; } void NameAndTypeResolver::checkTypeRequirements(ContractDefinition& _contract) @@ -69,7 +86,7 @@ void NameAndTypeResolver::checkTypeRequirements(ContractDefinition& _contract) void NameAndTypeResolver::updateDeclaration(Declaration const& _declaration) { - m_scopes[nullptr].registerDeclaration(_declaration, true); + m_scopes[nullptr].registerDeclaration(_declaration, false, true); solAssert(_declaration.getScope() == nullptr, "Updated declaration outside global scope."); } @@ -86,6 +103,98 @@ Declaration const* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& return m_currentScope->resolveName(_name, _recursive); } +void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base) +{ + auto iterator = m_scopes.find(&_base); + solAssert(iterator != end(m_scopes), ""); + for (auto const& nameAndDeclaration: iterator->second.getDeclarations()) + { + Declaration const* declaration = nameAndDeclaration.second; + // Import if it was declared in the base, is not the constructor and is visible in derived classes + if (declaration->getScope() == &_base && declaration->getName() != _base.getName() && + declaration->isVisibleInDerivedContracts()) + m_currentScope->registerDeclaration(*declaration); + } +} + +void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract) const +{ + // order in the lists is from derived to base + // list of lists to linearize, the last element is the list of direct bases + list<list<ContractDefinition const*>> input(1, {}); + for (ASTPointer<InheritanceSpecifier> const& baseSpecifier: _contract.getBaseContracts()) + { + ASTPointer<Identifier> baseName = baseSpecifier->getName(); + ContractDefinition const* base = dynamic_cast<ContractDefinition const*>( + baseName->getReferencedDeclaration()); + if (!base) + BOOST_THROW_EXCEPTION(baseName->createTypeError("Contract expected.")); + // "push_front" has the effect that bases mentioned later can overwrite members of bases + // mentioned earlier + input.back().push_front(base); + vector<ContractDefinition const*> const& basesBases = base->getLinearizedBaseContracts(); + if (basesBases.empty()) + BOOST_THROW_EXCEPTION(baseName->createTypeError("Definition of base has to precede definition of derived contract")); + input.push_front(list<ContractDefinition const*>(basesBases.begin(), basesBases.end())); + } + input.back().push_front(&_contract); + vector<ContractDefinition const*> result = cThreeMerge(input); + if (result.empty()) + BOOST_THROW_EXCEPTION(_contract.createTypeError("Linearization of inheritance graph impossible")); + _contract.setLinearizedBaseContracts(result); +} + +template <class _T> +vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMerge) +{ + // returns true iff _candidate appears only as last element of the lists + auto appearsOnlyAtHead = [&](_T const* _candidate) -> bool + { + for (list<_T const*> const& bases: _toMerge) + { + solAssert(!bases.empty(), ""); + if (find(++bases.begin(), bases.end(), _candidate) != bases.end()) + return false; + } + return true; + }; + // returns the next candidate to append to the linearized list or nullptr on failure + auto nextCandidate = [&]() -> _T const* + { + for (list<_T const*> const& bases: _toMerge) + { + solAssert(!bases.empty(), ""); + if (appearsOnlyAtHead(bases.front())) + return bases.front(); + } + return nullptr; + }; + // removes the given contract from all lists + auto removeCandidate = [&](_T const* _candidate) + { + for (auto it = _toMerge.begin(); it != _toMerge.end();) + { + it->remove(_candidate); + if (it->empty()) + it = _toMerge.erase(it); + else + ++it; + } + }; + + _toMerge.remove_if([](list<_T const*> const& _bases) { return _bases.empty(); }); + vector<_T const*> result; + while (!_toMerge.empty()) + { + _T const* candidate = nextCandidate(); + if (!candidate) + return vector<_T const*>(); + result.push_back(candidate); + removeCandidate(candidate); + } + return result; +} + DeclarationRegistrationHelper::DeclarationRegistrationHelper(map<ASTNode const*, DeclarationContainer>& _scopes, ASTNode& _astRoot): m_scopes(_scopes), m_currentScope(nullptr) @@ -115,6 +224,23 @@ void DeclarationRegistrationHelper::endVisit(StructDefinition&) closeCurrentScope(); } +bool DeclarationRegistrationHelper::visit(EnumDefinition& _enum) +{ + registerDeclaration(_enum, true); + return true; +} + +void DeclarationRegistrationHelper::endVisit(EnumDefinition&) +{ + closeCurrentScope(); +} + +bool DeclarationRegistrationHelper::visit(EnumValue& _value) +{ + registerDeclaration(_value, false); + return true; +} + bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function) { registerDeclaration(_function, true); @@ -128,6 +254,19 @@ void DeclarationRegistrationHelper::endVisit(FunctionDefinition&) closeCurrentScope(); } +bool DeclarationRegistrationHelper::visit(ModifierDefinition& _modifier) +{ + registerDeclaration(_modifier, true); + m_currentFunction = &_modifier; + return true; +} + +void DeclarationRegistrationHelper::endVisit(ModifierDefinition&) +{ + m_currentFunction = nullptr; + closeCurrentScope(); +} + void DeclarationRegistrationHelper::endVisit(VariableDefinition& _variableDefinition) { // Register the local variables with the function @@ -142,6 +281,17 @@ bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration) return true; } +bool DeclarationRegistrationHelper::visit(EventDefinition& _event) +{ + registerDeclaration(_event, true); + return true; +} + +void DeclarationRegistrationHelper::endVisit(EventDefinition&) +{ + closeCurrentScope(); +} + void DeclarationRegistrationHelper::enterNewSubScope(Declaration const& _declaration) { map<ASTNode const*, DeclarationContainer>::iterator iter; @@ -159,7 +309,7 @@ void DeclarationRegistrationHelper::closeCurrentScope() void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope) { - if (!m_scopes[m_currentScope].registerDeclaration(_declaration)) + if (!m_scopes[m_currentScope].registerDeclaration(_declaration, !_declaration.isVisibleInContract())) BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_sourceLocation(_declaration.getLocation()) << errinfo_comment("Identifier already declared.")); //@todo the exception should also contain the location of the first declaration @@ -169,8 +319,10 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio } ReferencesResolver::ReferencesResolver(ASTNode& _root, NameAndTypeResolver& _resolver, - ParameterList* _returnParameters, bool _allowLazyTypes): - m_resolver(_resolver), m_returnParameters(_returnParameters), m_allowLazyTypes(_allowLazyTypes) + ContractDefinition const* _currentContract, + ParameterList const* _returnParameters, bool _allowLazyTypes): + m_resolver(_resolver), m_currentContract(_currentContract), + m_returnParameters(_returnParameters), m_allowLazyTypes(_allowLazyTypes) { _root.accept(*this); } @@ -181,7 +333,13 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable) // or mapping if (_variable.getTypeName()) { - _variable.setType(_variable.getTypeName()->toType()); + TypePointer type = _variable.getTypeName()->toType(); + // All byte array parameter types should point to call data + if (_variable.isExternalFunctionParameter()) + if (auto const* byteArrayType = dynamic_cast<ByteArrayType const*>(type.get())) + type = byteArrayType->copyForLocation(ByteArrayType::Location::CallData); + _variable.setType(type); + if (!_variable.getType()) BOOST_THROW_EXCEPTION(_variable.getTypeName()->createTypeError("Invalid type name")); } @@ -192,8 +350,7 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable) bool ReferencesResolver::visit(Return& _return) { - solAssert(m_returnParameters, "Return parameters not set."); - _return.setFunctionReturnParameters(*m_returnParameters); + _return.setFunctionReturnParameters(m_returnParameters); return true; } @@ -218,7 +375,7 @@ bool ReferencesResolver::visit(Identifier& _identifier) if (!declaration) BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_sourceLocation(_identifier.getLocation()) << errinfo_comment("Undeclared identifier.")); - _identifier.setReferencedDeclaration(*declaration); + _identifier.setReferencedDeclaration(*declaration, m_currentContract); return false; } diff --git a/NameAndTypeResolver.h b/NameAndTypeResolver.h index 23ac5fe7..d9ac98ce 100644 --- a/NameAndTypeResolver.h +++ b/NameAndTypeResolver.h @@ -23,6 +23,7 @@ #pragma once #include <map> +#include <list> #include <boost/noncopyable.hpp> #include <libsolidity/DeclarationContainer.h> @@ -64,12 +65,23 @@ public: private: void reset(); + /// Imports all members declared directly in the given contract (i.e. does not import inherited + /// members) into the current scope if they are not present already. + void importInheritedScope(ContractDefinition const& _base); + + /// Computes "C3-Linearization" of base contracts and stores it inside the contract. + void linearizeBaseContracts(ContractDefinition& _contract) const; + /// Computes the C3-merge of the given list of lists of bases. + /// @returns the linearized vector or an empty vector if linearization is not possible. + template <class _T> + static std::vector<_T const*> cThreeMerge(std::list<std::list<_T const*>>& _toMerge); + /// Maps nodes declaring a scope to scopes, i.e. ContractDefinition and FunctionDeclaration, /// where nullptr denotes the global scope. Note that structs are not scope since they do /// not contain code. std::map<ASTNode const*, DeclarationContainer> m_scopes; - DeclarationContainer* m_currentScope; + DeclarationContainer* m_currentScope = nullptr; }; /** @@ -82,14 +94,21 @@ public: DeclarationRegistrationHelper(std::map<ASTNode const*, DeclarationContainer>& _scopes, ASTNode& _astRoot); private: - bool visit(ContractDefinition& _contract); - void endVisit(ContractDefinition& _contract); - bool visit(StructDefinition& _struct); - void endVisit(StructDefinition& _struct); - bool visit(FunctionDefinition& _function); - void endVisit(FunctionDefinition& _function); - void endVisit(VariableDefinition& _variableDefinition); - bool visit(VariableDeclaration& _declaration); + bool visit(ContractDefinition& _contract) override; + void endVisit(ContractDefinition& _contract) override; + bool visit(StructDefinition& _struct) override; + void endVisit(StructDefinition& _struct) override; + bool visit(EnumDefinition& _enum) override; + void endVisit(EnumDefinition& _enum) override; + bool visit(EnumValue& _value) override; + bool visit(FunctionDefinition& _function) override; + void endVisit(FunctionDefinition& _function) override; + bool visit(ModifierDefinition& _modifier) override; + void endVisit(ModifierDefinition& _modifier) override; + void endVisit(VariableDefinition& _variableDefinition) override; + bool visit(VariableDeclaration& _declaration) override; + bool visit(EventDefinition& _event) override; + void endVisit(EventDefinition& _event) override; void enterNewSubScope(Declaration const& _declaration); void closeCurrentScope(); @@ -97,7 +116,7 @@ private: std::map<ASTNode const*, DeclarationContainer>& m_scopes; Declaration const* m_currentScope; - FunctionDefinition* m_currentFunction; + VariableScope* m_currentFunction; }; /** @@ -108,7 +127,9 @@ class ReferencesResolver: private ASTVisitor { public: ReferencesResolver(ASTNode& _root, NameAndTypeResolver& _resolver, - ParameterList* _returnParameters, bool _allowLazyTypes = true); + ContractDefinition const* _currentContract, + ParameterList const* _returnParameters, + bool _allowLazyTypes = true); private: virtual void endVisit(VariableDeclaration& _variable) override; @@ -118,7 +139,8 @@ private: virtual bool visit(Return& _return) override; NameAndTypeResolver& m_resolver; - ParameterList* m_returnParameters; + ContractDefinition const* m_currentContract; + ParameterList const* m_returnParameters; bool m_allowLazyTypes; }; @@ -69,10 +69,10 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner) { switch (m_scanner->getCurrentToken()) { - case Token::IMPORT: + case Token::Import: nodes.push_back(parseImportDirective()); break; - case Token::CONTRACT: + case Token::Contract: nodes.push_back(parseContractDefinition()); break; default: @@ -100,119 +100,298 @@ int Parser::getEndPosition() const ASTPointer<ImportDirective> Parser::parseImportDirective() { ASTNodeFactory nodeFactory(*this); - expectToken(Token::IMPORT); - if (m_scanner->getCurrentToken() != Token::STRING_LITERAL) + expectToken(Token::Import); + if (m_scanner->getCurrentToken() != Token::StringLiteral) BOOST_THROW_EXCEPTION(createParserError("Expected string literal (URL).")); ASTPointer<ASTString> url = getLiteralAndAdvance(); nodeFactory.markEndPosition(); - expectToken(Token::SEMICOLON); + expectToken(Token::Semicolon); return nodeFactory.createNode<ImportDirective>(url); } ASTPointer<ContractDefinition> Parser::parseContractDefinition() { ASTNodeFactory nodeFactory(*this); - ASTPointer<ASTString> docstring; + ASTPointer<ASTString> docString; if (m_scanner->getCurrentCommentLiteral() != "") - docstring = make_shared<ASTString>(m_scanner->getCurrentCommentLiteral()); - expectToken(Token::CONTRACT); + docString = make_shared<ASTString>(m_scanner->getCurrentCommentLiteral()); + expectToken(Token::Contract); ASTPointer<ASTString> name = expectIdentifierToken(); - expectToken(Token::LBRACE); + vector<ASTPointer<InheritanceSpecifier>> baseContracts; vector<ASTPointer<StructDefinition>> structs; + vector<ASTPointer<EnumDefinition>> enums; vector<ASTPointer<VariableDeclaration>> stateVariables; vector<ASTPointer<FunctionDefinition>> functions; - bool visibilityIsPublic = true; + vector<ASTPointer<ModifierDefinition>> modifiers; + vector<ASTPointer<EventDefinition>> events; + if (m_scanner->getCurrentToken() == Token::Is) + do + { + m_scanner->next(); + baseContracts.push_back(parseInheritanceSpecifier()); + } + while (m_scanner->getCurrentToken() == Token::Comma); + expectToken(Token::LBrace); while (true) { Token::Value currentToken = m_scanner->getCurrentToken(); - if (currentToken == Token::RBRACE) + if (currentToken == Token::RBrace) break; - else if (currentToken == Token::PUBLIC || currentToken == Token::PRIVATE) - { - visibilityIsPublic = (m_scanner->getCurrentToken() == Token::PUBLIC); - m_scanner->next(); - expectToken(Token::COLON); - } - else if (currentToken == Token::FUNCTION) - functions.push_back(parseFunctionDefinition(visibilityIsPublic)); - else if (currentToken == Token::STRUCT) + else if (currentToken == Token::Function) + functions.push_back(parseFunctionDefinition(name.get())); + else if (currentToken == Token::Struct) structs.push_back(parseStructDefinition()); - else if (currentToken == Token::IDENTIFIER || currentToken == Token::MAPPING || + else if (currentToken == Token::Enum) + enums.push_back(parseEnumDefinition()); + else if (currentToken == Token::Identifier || currentToken == Token::Mapping || Token::isElementaryTypeName(currentToken)) { - bool const allowVar = false; - stateVariables.push_back(parseVariableDeclaration(allowVar)); - expectToken(Token::SEMICOLON); + VarDeclParserOptions options; + options.isStateVariable = true; + stateVariables.push_back(parseVariableDeclaration(options)); + expectToken(Token::Semicolon); } + else if (currentToken == Token::Modifier) + modifiers.push_back(parseModifierDefinition()); + else if (currentToken == Token::Event) + events.push_back(parseEventDefinition()); else - BOOST_THROW_EXCEPTION(createParserError("Function, variable or struct declaration expected.")); + BOOST_THROW_EXCEPTION(createParserError("Function, variable, struct or modifier declaration expected.")); } nodeFactory.markEndPosition(); - expectToken(Token::RBRACE); - return nodeFactory.createNode<ContractDefinition>(name, docstring, structs, stateVariables, functions); + expectToken(Token::RBrace); + return nodeFactory.createNode<ContractDefinition>(name, docString, baseContracts, structs, enums, + stateVariables, functions, modifiers, events); } -ASTPointer<FunctionDefinition> Parser::parseFunctionDefinition(bool _isPublic) +ASTPointer<InheritanceSpecifier> Parser::parseInheritanceSpecifier() +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer<Identifier> name(parseIdentifier()); + vector<ASTPointer<Expression>> arguments; + if (m_scanner->getCurrentToken() == Token::LParen) + { + m_scanner->next(); + arguments = parseFunctionCallListArguments(); + nodeFactory.markEndPosition(); + expectToken(Token::RParen); + } + else + nodeFactory.setEndPositionFromNode(name); + return nodeFactory.createNode<InheritanceSpecifier>(name, arguments); +} + +Declaration::Visibility Parser::parseVisibilitySpecifier(Token::Value _token) +{ + Declaration::Visibility visibility(Declaration::Visibility::Default); + if (_token == Token::Public) + visibility = Declaration::Visibility::Public; + else if (_token == Token::Inheritable) + visibility = Declaration::Visibility::Inheritable; + else if (_token == Token::Private) + visibility = Declaration::Visibility::Private; + else if (_token == Token::External) + visibility = Declaration::Visibility::External; + else + solAssert(false, "Invalid visibility specifier."); + m_scanner->next(); + return visibility; +} + +ASTPointer<FunctionDefinition> Parser::parseFunctionDefinition(ASTString const* _contractName) { ASTNodeFactory nodeFactory(*this); ASTPointer<ASTString> docstring; if (m_scanner->getCurrentCommentLiteral() != "") docstring = make_shared<ASTString>(m_scanner->getCurrentCommentLiteral()); - expectToken(Token::FUNCTION); - ASTPointer<ASTString> name(expectIdentifierToken()); + expectToken(Token::Function); + ASTPointer<ASTString> name; + if (m_scanner->getCurrentToken() == Token::LParen) + name = make_shared<ASTString>(); // anonymous function + else + name = expectIdentifierToken(); ASTPointer<ParameterList> parameters(parseParameterList()); bool isDeclaredConst = false; - if (m_scanner->getCurrentToken() == Token::CONST) + Declaration::Visibility visibility(Declaration::Visibility::Default); + vector<ASTPointer<ModifierInvocation>> modifiers; + while (true) { - isDeclaredConst = true; - m_scanner->next(); + Token::Value token = m_scanner->getCurrentToken(); + if (token == Token::Const) + { + isDeclaredConst = true; + m_scanner->next(); + } + else if (token == Token::Identifier) + modifiers.push_back(parseModifierInvocation()); + else if (Token::isVisibilitySpecifier(token)) + { + if (visibility != Declaration::Visibility::Default) + BOOST_THROW_EXCEPTION(createParserError("Multiple visibility specifiers.")); + visibility = parseVisibilitySpecifier(token); + } + else + break; } ASTPointer<ParameterList> returnParameters; - if (m_scanner->getCurrentToken() == Token::RETURNS) + if (m_scanner->getCurrentToken() == Token::Returns) { bool const permitEmptyParameterList = false; m_scanner->next(); returnParameters = parseParameterList(permitEmptyParameterList); } else - { - // create an empty parameter list at a zero-length location - ASTNodeFactory nodeFactory(*this); - nodeFactory.setLocationEmpty(); - returnParameters = nodeFactory.createNode<ParameterList>(vector<ASTPointer<VariableDeclaration>>()); - } + returnParameters = createEmptyParameterList(); ASTPointer<Block> block = parseBlock(); nodeFactory.setEndPositionFromNode(block); - return nodeFactory.createNode<FunctionDefinition>(name, _isPublic, docstring, - parameters, - isDeclaredConst, returnParameters, block); + bool const c_isConstructor = (_contractName && *name == *_contractName); + return nodeFactory.createNode<FunctionDefinition>(name, visibility, c_isConstructor, docstring, + parameters, isDeclaredConst, modifiers, + returnParameters, block); } ASTPointer<StructDefinition> Parser::parseStructDefinition() { ASTNodeFactory nodeFactory(*this); - expectToken(Token::STRUCT); + expectToken(Token::Struct); ASTPointer<ASTString> name = expectIdentifierToken(); vector<ASTPointer<VariableDeclaration>> members; - expectToken(Token::LBRACE); - while (m_scanner->getCurrentToken() != Token::RBRACE) + expectToken(Token::LBrace); + while (m_scanner->getCurrentToken() != Token::RBrace) { - bool const allowVar = false; - members.push_back(parseVariableDeclaration(allowVar)); - expectToken(Token::SEMICOLON); + members.push_back(parseVariableDeclaration()); + expectToken(Token::Semicolon); } nodeFactory.markEndPosition(); - expectToken(Token::RBRACE); + expectToken(Token::RBrace); return nodeFactory.createNode<StructDefinition>(name, members); } -ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(bool _allowVar) +ASTPointer<EnumValue> Parser::parseEnumValue() { ASTNodeFactory nodeFactory(*this); - ASTPointer<TypeName> type = parseTypeName(_allowVar); nodeFactory.markEndPosition(); - return nodeFactory.createNode<VariableDeclaration>(type, expectIdentifierToken()); + return nodeFactory.createNode<EnumValue>(expectIdentifierToken()); +} + +ASTPointer<EnumDefinition> Parser::parseEnumDefinition() +{ + ASTNodeFactory nodeFactory(*this); + expectToken(Token::Enum); + ASTPointer<ASTString> name = expectIdentifierToken(); + vector<ASTPointer<EnumValue>> members; + expectToken(Token::LBrace); + + while (m_scanner->getCurrentToken() != Token::RBrace) + { + members.push_back(parseEnumValue()); + if (m_scanner->getCurrentToken() == Token::RBrace) + break; + expectToken(Token::Comma); + if (m_scanner->getCurrentToken() != Token::Identifier) + BOOST_THROW_EXCEPTION(createParserError("Expected Identifier after ','")); + } + + nodeFactory.markEndPosition(); + expectToken(Token::RBrace); + return nodeFactory.createNode<EnumDefinition>(name, members); +} + +ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(VarDeclParserOptions const& _options) +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer<TypeName> type = parseTypeName(_options.allowVar); + if (type != nullptr) + nodeFactory.setEndPositionFromNode(type); + bool isIndexed = false; + ASTPointer<ASTString> identifier; + Token::Value token = m_scanner->getCurrentToken(); + Declaration::Visibility visibility(Declaration::Visibility::Default); + if (_options.isStateVariable && Token::isVariableVisibilitySpecifier(token)) + visibility = parseVisibilitySpecifier(token); + if (_options.allowIndexed && token == Token::Indexed) + { + isIndexed = true; + m_scanner->next(); + } + nodeFactory.markEndPosition(); + if (_options.allowEmptyName && m_scanner->getCurrentToken() != Token::Identifier) + { + identifier = make_shared<ASTString>(""); + solAssert(type != nullptr, ""); + nodeFactory.setEndPositionFromNode(type); + } + else + identifier = expectIdentifierToken(); + return nodeFactory.createNode<VariableDeclaration>(type, identifier, + visibility, _options.isStateVariable, + isIndexed); +} + +ASTPointer<ModifierDefinition> Parser::parseModifierDefinition() +{ + ScopeGuard resetModifierFlag([this]() { m_insideModifier = false; }); + m_insideModifier = true; + + ASTNodeFactory nodeFactory(*this); + ASTPointer<ASTString> docstring; + if (m_scanner->getCurrentCommentLiteral() != "") + docstring = make_shared<ASTString>(m_scanner->getCurrentCommentLiteral()); + + expectToken(Token::Modifier); + ASTPointer<ASTString> name(expectIdentifierToken()); + ASTPointer<ParameterList> parameters; + if (m_scanner->getCurrentToken() == Token::LParen) + parameters = parseParameterList(); + else + parameters = createEmptyParameterList(); + ASTPointer<Block> block = parseBlock(); + nodeFactory.setEndPositionFromNode(block); + return nodeFactory.createNode<ModifierDefinition>(name, docstring, parameters, block); +} + +ASTPointer<EventDefinition> Parser::parseEventDefinition() +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer<ASTString> docstring; + if (m_scanner->getCurrentCommentLiteral() != "") + docstring = make_shared<ASTString>(m_scanner->getCurrentCommentLiteral()); + + expectToken(Token::Event); + ASTPointer<ASTString> name(expectIdentifierToken()); + ASTPointer<ParameterList> parameters; + if (m_scanner->getCurrentToken() == Token::LParen) + parameters = parseParameterList(true, true); + else + parameters = createEmptyParameterList(); + nodeFactory.markEndPosition(); + expectToken(Token::Semicolon); + return nodeFactory.createNode<EventDefinition>(name, docstring, parameters); +} + +ASTPointer<ModifierInvocation> Parser::parseModifierInvocation() +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer<Identifier> name(parseIdentifier()); + vector<ASTPointer<Expression>> arguments; + if (m_scanner->getCurrentToken() == Token::LParen) + { + m_scanner->next(); + arguments = parseFunctionCallListArguments(); + nodeFactory.markEndPosition(); + expectToken(Token::RParen); + } + else + nodeFactory.setEndPositionFromNode(name); + return nodeFactory.createNode<ModifierInvocation>(name, arguments); +} + +ASTPointer<Identifier> Parser::parseIdentifier() +{ + ASTNodeFactory nodeFactory(*this); + nodeFactory.markEndPosition(); + return nodeFactory.createNode<Identifier>(expectIdentifierToken()); } ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar) @@ -224,17 +403,17 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar) type = ASTNodeFactory(*this).createNode<ElementaryTypeName>(token); m_scanner->next(); } - else if (token == Token::VAR) + else if (token == Token::Var) { if (!_allowVar) BOOST_THROW_EXCEPTION(createParserError("Expected explicit type name.")); m_scanner->next(); } - else if (token == Token::MAPPING) + else if (token == Token::Mapping) { type = parseMapping(); } - else if (token == Token::IDENTIFIER) + else if (token == Token::Identifier) { ASTNodeFactory nodeFactory(*this); nodeFactory.markEndPosition(); @@ -248,34 +427,36 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar) ASTPointer<Mapping> Parser::parseMapping() { ASTNodeFactory nodeFactory(*this); - expectToken(Token::MAPPING); - expectToken(Token::LPAREN); + expectToken(Token::Mapping); + expectToken(Token::LParen); if (!Token::isElementaryTypeName(m_scanner->getCurrentToken())) BOOST_THROW_EXCEPTION(createParserError("Expected elementary type name for mapping key type")); ASTPointer<ElementaryTypeName> keyType; keyType = ASTNodeFactory(*this).createNode<ElementaryTypeName>(m_scanner->getCurrentToken()); m_scanner->next(); - expectToken(Token::ARROW); + expectToken(Token::Arrow); bool const allowVar = false; ASTPointer<TypeName> valueType = parseTypeName(allowVar); nodeFactory.markEndPosition(); - expectToken(Token::RPAREN); + expectToken(Token::RParen); return nodeFactory.createNode<Mapping>(keyType, valueType); } -ASTPointer<ParameterList> Parser::parseParameterList(bool _allowEmpty) +ASTPointer<ParameterList> Parser::parseParameterList(bool _allowEmpty, bool _allowIndexed) { ASTNodeFactory nodeFactory(*this); vector<ASTPointer<VariableDeclaration>> parameters; - expectToken(Token::LPAREN); - if (!_allowEmpty || m_scanner->getCurrentToken() != Token::RPAREN) + VarDeclParserOptions options; + options.allowIndexed = _allowIndexed; + options.allowEmptyName = true; + expectToken(Token::LParen); + if (!_allowEmpty || m_scanner->getCurrentToken() != Token::RParen) { - bool const allowVar = false; - parameters.push_back(parseVariableDeclaration(allowVar)); - while (m_scanner->getCurrentToken() != Token::RPAREN) + parameters.push_back(parseVariableDeclaration(options)); + while (m_scanner->getCurrentToken() != Token::RParen) { - expectToken(Token::COMMA); - parameters.push_back(parseVariableDeclaration(allowVar)); + expectToken(Token::Comma); + parameters.push_back(parseVariableDeclaration(options)); } } nodeFactory.markEndPosition(); @@ -286,12 +467,12 @@ ASTPointer<ParameterList> Parser::parseParameterList(bool _allowEmpty) ASTPointer<Block> Parser::parseBlock() { ASTNodeFactory nodeFactory(*this); - expectToken(Token::LBRACE); + expectToken(Token::LBrace); vector<ASTPointer<Statement>> statements; - while (m_scanner->getCurrentToken() != Token::RBRACE) + while (m_scanner->getCurrentToken() != Token::RBrace) statements.push_back(parseStatement()); nodeFactory.markEndPosition(); - expectToken(Token::RBRACE); + expectToken(Token::RBrace); return nodeFactory.createNode<Block>(statements); } @@ -300,52 +481,60 @@ ASTPointer<Statement> Parser::parseStatement() ASTPointer<Statement> statement; switch (m_scanner->getCurrentToken()) { - case Token::IF: + case Token::If: return parseIfStatement(); - case Token::WHILE: + case Token::While: return parseWhileStatement(); - case Token::FOR: + case Token::For: return parseForStatement(); - case Token::LBRACE: + case Token::LBrace: return parseBlock(); // starting from here, all statements must be terminated by a semicolon - case Token::CONTINUE: + case Token::Continue: statement = ASTNodeFactory(*this).createNode<Continue>(); m_scanner->next(); break; - case Token::BREAK: + case Token::Break: statement = ASTNodeFactory(*this).createNode<Break>(); m_scanner->next(); break; - case Token::RETURN: + case Token::Return: { ASTNodeFactory nodeFactory(*this); ASTPointer<Expression> expression; - if (m_scanner->next() != Token::SEMICOLON) + if (m_scanner->next() != Token::Semicolon) { expression = parseExpression(); nodeFactory.setEndPositionFromNode(expression); } statement = nodeFactory.createNode<Return>(expression); + break; } - break; + case Token::Identifier: + if (m_insideModifier && m_scanner->getCurrentLiteral() == "_") + { + statement = ASTNodeFactory(*this).createNode<PlaceholderStatement>(); + m_scanner->next(); + return statement; + } + // fall-through default: statement = parseVarDefOrExprStmt(); } - expectToken(Token::SEMICOLON); + expectToken(Token::Semicolon); return statement; } ASTPointer<IfStatement> Parser::parseIfStatement() { ASTNodeFactory nodeFactory(*this); - expectToken(Token::IF); - expectToken(Token::LPAREN); + expectToken(Token::If); + expectToken(Token::LParen); ASTPointer<Expression> condition = parseExpression(); - expectToken(Token::RPAREN); + expectToken(Token::RParen); ASTPointer<Statement> trueBody = parseStatement(); ASTPointer<Statement> falseBody; - if (m_scanner->getCurrentToken() == Token::ELSE) + if (m_scanner->getCurrentToken() == Token::Else) { m_scanner->next(); falseBody = parseStatement(); @@ -359,10 +548,10 @@ ASTPointer<IfStatement> Parser::parseIfStatement() ASTPointer<WhileStatement> Parser::parseWhileStatement() { ASTNodeFactory nodeFactory(*this); - expectToken(Token::WHILE); - expectToken(Token::LPAREN); + expectToken(Token::While); + expectToken(Token::LParen); ASTPointer<Expression> condition = parseExpression(); - expectToken(Token::RPAREN); + expectToken(Token::RParen); ASTPointer<Statement> body = parseStatement(); nodeFactory.setEndPositionFromNode(body); return nodeFactory.createNode<WhileStatement>(condition, body); @@ -374,21 +563,21 @@ ASTPointer<ForStatement> Parser::parseForStatement() ASTPointer<Statement> initExpression; ASTPointer<Expression> conditionExpression; ASTPointer<ExpressionStatement> loopExpression; - expectToken(Token::FOR); - expectToken(Token::LPAREN); + expectToken(Token::For); + expectToken(Token::LParen); - // LTODO: Maybe here have some predicate like peekExpression() instead of checking for semicolon and RPAREN? - if (m_scanner->getCurrentToken() != Token::SEMICOLON) + // LTODO: Maybe here have some predicate like peekExpression() instead of checking for semicolon and RParen? + if (m_scanner->getCurrentToken() != Token::Semicolon) initExpression = parseVarDefOrExprStmt(); - expectToken(Token::SEMICOLON); + expectToken(Token::Semicolon); - if (m_scanner->getCurrentToken() != Token::SEMICOLON) + if (m_scanner->getCurrentToken() != Token::Semicolon) conditionExpression = parseExpression(); - expectToken(Token::SEMICOLON); + expectToken(Token::Semicolon); - if (m_scanner->getCurrentToken() != Token::RPAREN) + if (m_scanner->getCurrentToken() != Token::RParen) loopExpression = parseExpressionStatement(); - expectToken(Token::RPAREN); + expectToken(Token::RParen); ASTPointer<Statement> body = parseStatement(); nodeFactory.setEndPositionFromNode(body); @@ -409,10 +598,11 @@ ASTPointer<Statement> Parser::parseVarDefOrExprStmt() ASTPointer<VariableDefinition> Parser::parseVariableDefinition() { ASTNodeFactory nodeFactory(*this); - bool const allowVar = true; - ASTPointer<VariableDeclaration> variable = parseVariableDeclaration(allowVar); + VarDeclParserOptions options; + options.allowVar = true; + ASTPointer<VariableDeclaration> variable = parseVariableDeclaration(options); ASTPointer<Expression> value; - if (m_scanner->getCurrentToken() == Token::ASSIGN) + if (m_scanner->getCurrentToken() == Token::Assign) { m_scanner->next(); value = parseExpression(); @@ -449,7 +639,6 @@ ASTPointer<Expression> Parser::parseBinaryExpression(int _minPrecedence) ASTPointer<Expression> expression = parseUnaryExpression(); int precedence = Token::precedence(m_scanner->getCurrentToken()); for (; precedence >= _minPrecedence; --precedence) - { while (Token::precedence(m_scanner->getCurrentToken()) == precedence) { Token::Value op = m_scanner->getCurrentToken(); @@ -458,7 +647,6 @@ ASTPointer<Expression> Parser::parseBinaryExpression(int _minPrecedence) nodeFactory.setEndPositionFromNode(right); expression = nodeFactory.createNode<BinaryOperation>(expression, op, right); } - } return expression; } @@ -466,17 +654,7 @@ ASTPointer<Expression> Parser::parseUnaryExpression() { ASTNodeFactory nodeFactory(*this); Token::Value token = m_scanner->getCurrentToken(); - if (token == Token::NEW) - { - expectToken(Token::NEW); - ASTPointer<Identifier> contractName = ASTNodeFactory(*this).createNode<Identifier>(expectIdentifierToken()); - expectToken(Token::LPAREN); - vector<ASTPointer<Expression>> arguments(parseFunctionCallArguments()); - expectToken(Token::RPAREN); - nodeFactory.markEndPosition(); - return nodeFactory.createNode<NewExpression>(contractName, arguments); - } - else if (Token::isUnaryOp(token) || Token::isCountOp(token)) + if (Token::isUnaryOp(token) || Token::isCountOp(token)) { // prefix expression m_scanner->next(); @@ -500,34 +678,46 @@ ASTPointer<Expression> Parser::parseUnaryExpression() ASTPointer<Expression> Parser::parseLeftHandSideExpression() { ASTNodeFactory nodeFactory(*this); - ASTPointer<Expression> expression = parsePrimaryExpression(); + ASTPointer<Expression> expression; + if (m_scanner->getCurrentToken() == Token::New) + { + expectToken(Token::New); + ASTPointer<Identifier> contractName(parseIdentifier()); + nodeFactory.setEndPositionFromNode(contractName); + expression = nodeFactory.createNode<NewExpression>(contractName); + } + else + expression = parsePrimaryExpression(); + while (true) { switch (m_scanner->getCurrentToken()) { - case Token::LBRACK: + case Token::LBrack: { m_scanner->next(); ASTPointer<Expression> index = parseExpression(); nodeFactory.markEndPosition(); - expectToken(Token::RBRACK); + expectToken(Token::RBrack); expression = nodeFactory.createNode<IndexAccess>(expression, index); } break; - case Token::PERIOD: + case Token::Period: { m_scanner->next(); nodeFactory.markEndPosition(); expression = nodeFactory.createNode<MemberAccess>(expression, expectIdentifierToken()); } break; - case Token::LPAREN: + case Token::LParen: { m_scanner->next(); - vector<ASTPointer<Expression>> arguments = parseFunctionCallArguments(); + vector<ASTPointer<Expression>> arguments; + vector<ASTPointer<ASTString>> names; + std::tie(arguments, names) = parseFunctionCallArguments(); nodeFactory.markEndPosition(); - expectToken(Token::RPAREN); - expression = nodeFactory.createNode<FunctionCall>(expression, arguments); + expectToken(Token::RParen); + expression = nodeFactory.createNode<FunctionCall>(expression, arguments, names); } break; default: @@ -543,24 +733,34 @@ ASTPointer<Expression> Parser::parsePrimaryExpression() ASTPointer<Expression> expression; switch (token) { - case Token::TRUE_LITERAL: - case Token::FALSE_LITERAL: + case Token::TrueLiteral: + case Token::FalseLiteral: expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance()); break; - case Token::NUMBER: - case Token::STRING_LITERAL: + case Token::Number: + if (Token::isEtherSubdenomination(m_scanner->peekNextToken())) + { + ASTPointer<ASTString> literal = getLiteralAndAdvance(); + nodeFactory.markEndPosition(); + Literal::SubDenomination subdenomination = static_cast<Literal::SubDenomination>(m_scanner->getCurrentToken()); + m_scanner->next(); + expression = nodeFactory.createNode<Literal>(token, literal, subdenomination); + break; + } + // fall-through + case Token::StringLiteral: nodeFactory.markEndPosition(); expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance()); break; - case Token::IDENTIFIER: + case Token::Identifier: nodeFactory.markEndPosition(); expression = nodeFactory.createNode<Identifier>(getLiteralAndAdvance()); break; - case Token::LPAREN: + case Token::LParen: { m_scanner->next(); ASTPointer<Expression> expression = parseExpression(); - expectToken(Token::RPAREN); + expectToken(Token::RParen); return expression; } default: @@ -580,21 +780,47 @@ ASTPointer<Expression> Parser::parsePrimaryExpression() return expression; } -vector<ASTPointer<Expression>> Parser::parseFunctionCallArguments() +vector<ASTPointer<Expression>> Parser::parseFunctionCallListArguments() { vector<ASTPointer<Expression>> arguments; - if (m_scanner->getCurrentToken() != Token::RPAREN) + if (m_scanner->getCurrentToken() != Token::RParen) { arguments.push_back(parseExpression()); - while (m_scanner->getCurrentToken() != Token::RPAREN) + while (m_scanner->getCurrentToken() != Token::RParen) { - expectToken(Token::COMMA); + expectToken(Token::Comma); arguments.push_back(parseExpression()); } } return arguments; } +pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::parseFunctionCallArguments() +{ + pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> ret; + Token::Value token = m_scanner->getCurrentToken(); + if (token == Token::LBrace) + { + // call({arg1 : 1, arg2 : 2 }) + expectToken(Token::LBrace); + while (m_scanner->getCurrentToken() != Token::RBrace) + { + ret.second.push_back(expectIdentifierToken()); + expectToken(Token::Colon); + ret.first.push_back(parseExpression()); + + if (m_scanner->getCurrentToken() == Token::Comma) + expectToken(Token::Comma); + else + break; + } + expectToken(Token::RBrace); + } + else + ret.first = parseFunctionCallListArguments(); + return ret; +} + bool Parser::peekVariableDefinition() { @@ -602,11 +828,11 @@ bool Parser::peekVariableDefinition() // (which include assignments to other expressions and pre-declared variables) // We have a variable definition if we get a keyword that specifies a type name, or // in the case of a user-defined type, we have two identifiers following each other. - return (m_scanner->getCurrentToken() == Token::MAPPING || - m_scanner->getCurrentToken() == Token::VAR || + return (m_scanner->getCurrentToken() == Token::Mapping || + m_scanner->getCurrentToken() == Token::Var || ((Token::isElementaryTypeName(m_scanner->getCurrentToken()) || - m_scanner->getCurrentToken() == Token::IDENTIFIER) && - m_scanner->peekNextToken() == Token::IDENTIFIER)); + m_scanner->getCurrentToken() == Token::Identifier) && + m_scanner->peekNextToken() == Token::Identifier)); } void Parser::expectToken(Token::Value _value) @@ -627,7 +853,7 @@ Token::Value Parser::expectAssignmentOperator() ASTPointer<ASTString> Parser::expectIdentifierToken() { - if (m_scanner->getCurrentToken() != Token::IDENTIFIER) + if (m_scanner->getCurrentToken() != Token::Identifier) BOOST_THROW_EXCEPTION(createParserError("Expected identifier")); return getLiteralAndAdvance(); } @@ -639,6 +865,13 @@ ASTPointer<ASTString> Parser::getLiteralAndAdvance() return identifier; } +ASTPointer<ParameterList> Parser::createEmptyParameterList() +{ + ASTNodeFactory nodeFactory(*this); + nodeFactory.setLocationEmpty(); + return nodeFactory.createNode<ParameterList>(vector<ASTPointer<VariableDeclaration>>()); +} + ParserError Parser::createParserError(string const& _description) const { return ParserError() << errinfo_sourceLocation(Location(getPosition(), getPosition(), getSourceName())) @@ -45,16 +45,32 @@ private: /// End position of the current token int getEndPosition() const; + struct VarDeclParserOptions { + VarDeclParserOptions() {} + bool allowVar = false; + bool isStateVariable = false; + bool allowIndexed = false; + bool allowEmptyName = false; + }; + ///@{ ///@name Parsing functions for the AST nodes ASTPointer<ImportDirective> parseImportDirective(); ASTPointer<ContractDefinition> parseContractDefinition(); - ASTPointer<FunctionDefinition> parseFunctionDefinition(bool _isPublic); + ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier(); + Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); + ASTPointer<FunctionDefinition> parseFunctionDefinition(ASTString const* _contractName); ASTPointer<StructDefinition> parseStructDefinition(); - ASTPointer<VariableDeclaration> parseVariableDeclaration(bool _allowVar); + ASTPointer<EnumDefinition> parseEnumDefinition(); + ASTPointer<EnumValue> parseEnumValue(); + ASTPointer<VariableDeclaration> parseVariableDeclaration(VarDeclParserOptions const& _options = VarDeclParserOptions()); + ASTPointer<ModifierDefinition> parseModifierDefinition(); + ASTPointer<EventDefinition> parseEventDefinition(); + ASTPointer<ModifierInvocation> parseModifierInvocation(); + ASTPointer<Identifier> parseIdentifier(); ASTPointer<TypeName> parseTypeName(bool _allowVar); ASTPointer<Mapping> parseMapping(); - ASTPointer<ParameterList> parseParameterList(bool _allowEmpty = true); + ASTPointer<ParameterList> parseParameterList(bool _allowEmpty = true, bool _allowIndexed = false); ASTPointer<Block> parseBlock(); ASTPointer<Statement> parseStatement(); ASTPointer<IfStatement> parseIfStatement(); @@ -68,7 +84,8 @@ private: ASTPointer<Expression> parseUnaryExpression(); ASTPointer<Expression> parseLeftHandSideExpression(); ASTPointer<Expression> parsePrimaryExpression(); - std::vector<ASTPointer<Expression>> parseFunctionCallArguments(); + std::vector<ASTPointer<Expression>> parseFunctionCallListArguments(); + std::pair<std::vector<ASTPointer<Expression>>, std::vector<ASTPointer<ASTString>>> parseFunctionCallArguments(); ///@} ///@{ @@ -84,11 +101,16 @@ private: ASTPointer<ASTString> getLiteralAndAdvance(); ///@} + /// Creates an empty ParameterList at the current location (used if parameters can be omitted). + ASTPointer<ParameterList> createEmptyParameterList(); + /// Creates a @ref ParserError exception and annotates it with the current position and the /// given @a _description. ParserError createParserError(std::string const& _description) const; std::shared_ptr<Scanner> m_scanner; + /// Flag that signifies whether '_' is parsed as a PlaceholderStatement or a regular identifier. + bool m_insideModifier = false; }; } diff --git a/Scanner.cpp b/Scanner.cpp index f22e69bc..fbe3ea97 100644 --- a/Scanner.cpp +++ b/Scanner.cpp @@ -225,7 +225,7 @@ Token::Value Scanner::skipSingleLineComment() // separately by the lexical grammar and becomes part of the // stream of input elements for the syntactic grammar while (advance() && !isLineTerminator(m_char)) { }; - return Token::WHITESPACE; + return Token::Whitespace; } Token::Value Scanner::scanSingleLineDocComment() @@ -255,7 +255,7 @@ Token::Value Scanner::scanSingleLineDocComment() advance(); } literal.complete(); - return Token::COMMENT_LITERAL; + return Token::CommentLiteral; } Token::Value Scanner::skipMultiLineComment() @@ -272,11 +272,11 @@ Token::Value Scanner::skipMultiLineComment() if (ch == '*' && m_char == '/') { m_char = ' '; - return Token::WHITESPACE; + return Token::Whitespace; } } // Unterminated multi-line comment. - return Token::ILLEGAL; + return Token::Illegal; } Token::Value Scanner::scanMultiLineDocComment() @@ -285,8 +285,6 @@ Token::Value Scanner::scanMultiLineDocComment() bool endFound = false; bool charsAdded = false; - advance(); //consume the last '*' at /** - skipWhitespaceExceptLF(); while (!isSourcePastEndOfInput()) { //handle newlines in multline comments @@ -321,9 +319,9 @@ Token::Value Scanner::scanMultiLineDocComment() } literal.complete(); if (!endFound) - return Token::ILLEGAL; + return Token::Illegal; else - return Token::COMMENT_LITERAL; + return Token::CommentLiteral; } Token::Value Scanner::scanSlash() @@ -333,7 +331,7 @@ Token::Value Scanner::scanSlash() if (m_char == '/') { if (!advance()) /* double slash comment directly before EOS */ - return Token::WHITESPACE; + return Token::Whitespace; else if (m_char == '/') { // doxygen style /// comment @@ -342,7 +340,7 @@ Token::Value Scanner::scanSlash() comment = scanSingleLineDocComment(); m_nextSkippedComment.location.end = getSourcePos(); m_nextSkippedComment.token = comment; - return Token::WHITESPACE; + return Token::Whitespace; } else return skipSingleLineComment(); @@ -351,23 +349,32 @@ Token::Value Scanner::scanSlash() { // doxygen style /** natspec comment if (!advance()) /* slash star comment before EOS */ - return Token::WHITESPACE; + return Token::Whitespace; else if (m_char == '*') { - Token::Value comment; - m_nextSkippedComment.location.start = firstSlashPosition; - comment = scanMultiLineDocComment(); - m_nextSkippedComment.location.end = getSourcePos(); - m_nextSkippedComment.token = comment; - return Token::WHITESPACE; + advance(); //consume the last '*' at /** + skipWhitespaceExceptLF(); + + // special case of a closed normal multiline comment + if (!m_source.isPastEndOfInput() && m_source.get(0) == '/') + advance(); //skip the closing slash + else // we actually have a multiline documentation comment + { + Token::Value comment; + m_nextSkippedComment.location.start = firstSlashPosition; + comment = scanMultiLineDocComment(); + m_nextSkippedComment.location.end = getSourcePos(); + m_nextSkippedComment.token = comment; + } + return Token::Whitespace; } else return skipMultiLineComment(); } else if (m_char == '=') - return selectToken(Token::ASSIGN_DIV); + return selectToken(Token::AssignDiv); else - return Token::DIV; + return Token::Div; } void Scanner::scanToken() @@ -384,7 +391,7 @@ void Scanner::scanToken() case '\n': // fall-through case ' ': case '\t': - token = selectToken(Token::WHITESPACE); + token = selectToken(Token::Whitespace); break; case '"': case '\'': @@ -394,81 +401,82 @@ void Scanner::scanToken() // < <= << <<= advance(); if (m_char == '=') - token = selectToken(Token::LTE); + token = selectToken(Token::LessThanOrEqual); else if (m_char == '<') - token = selectToken('=', Token::ASSIGN_SHL, Token::SHL); + token = selectToken('=', Token::AssignShl, Token::SHL); else - token = Token::LT; + token = Token::LessThan; break; case '>': // > >= >> >>= >>> >>>= advance(); if (m_char == '=') - token = selectToken(Token::GTE); + token = selectToken(Token::GreaterThanOrEqual); else if (m_char == '>') { // >> >>= >>> >>>= advance(); if (m_char == '=') - token = selectToken(Token::ASSIGN_SAR); + token = selectToken(Token::AssignSar); else if (m_char == '>') - token = selectToken('=', Token::ASSIGN_SHR, Token::SHR); + token = selectToken('=', Token::AssignShr, Token::SHR); else token = Token::SAR; } else - token = Token::GT; + token = Token::GreaterThan; break; case '=': // = == => advance(); if (m_char == '=') - token = selectToken(Token::EQ); + token = selectToken(Token::Equal); else if (m_char == '>') - token = selectToken(Token::ARROW); + token = selectToken(Token::Arrow); else - token = Token::ASSIGN; + token = Token::Assign; break; case '!': // ! != advance(); if (m_char == '=') - token = selectToken(Token::NE); + token = selectToken(Token::NotEqual); else - token = Token::NOT; + token = Token::Not; break; case '+': // + ++ += advance(); if (m_char == '+') - token = selectToken(Token::INC); + token = selectToken(Token::Inc); else if (m_char == '=') - token = selectToken(Token::ASSIGN_ADD); + token = selectToken(Token::AssignAdd); else - token = Token::ADD; + token = Token::Add; break; case '-': - // - -- -= Number + // - -- -= advance(); if (m_char == '-') - { - advance(); - token = Token::DEC; - } + token = selectToken(Token::Dec); else if (m_char == '=') - token = selectToken(Token::ASSIGN_SUB); - else if (m_char == '.' || isDecimalDigit(m_char)) - token = scanNumber('-'); + token = selectToken(Token::AssignSub); else - token = Token::SUB; + token = Token::Sub; break; case '*': - // * *= - token = selectToken('=', Token::ASSIGN_MUL, Token::MUL); + // * ** *= + advance(); + if (m_char == '*') + token = selectToken(Token::Exp); + else if (m_char == '=') + token = selectToken(Token::AssignMul); + else + token = Token::Mul; break; case '%': // % %= - token = selectToken('=', Token::ASSIGN_MOD, Token::MOD); + token = selectToken('=', Token::AssignMod, Token::Mod); break; case '/': // / // /* /= @@ -478,25 +486,25 @@ void Scanner::scanToken() // & && &= advance(); if (m_char == '&') - token = selectToken(Token::AND); + token = selectToken(Token::And); else if (m_char == '=') - token = selectToken(Token::ASSIGN_BIT_AND); + token = selectToken(Token::AssignBitAnd); else - token = Token::BIT_AND; + token = Token::BitAnd; break; case '|': // | || |= advance(); if (m_char == '|') - token = selectToken(Token::OR); + token = selectToken(Token::Or); else if (m_char == '=') - token = selectToken(Token::ASSIGN_BIT_OR); + token = selectToken(Token::AssignBitOr); else - token = Token::BIT_OR; + token = Token::BitOr; break; case '^': // ^ ^= - token = selectToken('=', Token::ASSIGN_BIT_XOR, Token::BIT_XOR); + token = selectToken('=', Token::AssignBitXor, Token::BitXor); break; case '.': // . Number @@ -504,40 +512,40 @@ void Scanner::scanToken() if (isDecimalDigit(m_char)) token = scanNumber('.'); else - token = Token::PERIOD; + token = Token::Period; break; case ':': - token = selectToken(Token::COLON); + token = selectToken(Token::Colon); break; case ';': - token = selectToken(Token::SEMICOLON); + token = selectToken(Token::Semicolon); break; case ',': - token = selectToken(Token::COMMA); + token = selectToken(Token::Comma); break; case '(': - token = selectToken(Token::LPAREN); + token = selectToken(Token::LParen); break; case ')': - token = selectToken(Token::RPAREN); + token = selectToken(Token::RParen); break; case '[': - token = selectToken(Token::LBRACK); + token = selectToken(Token::LBrack); break; case ']': - token = selectToken(Token::RBRACK); + token = selectToken(Token::RBrack); break; case '{': - token = selectToken(Token::LBRACE); + token = selectToken(Token::LBrace); break; case '}': - token = selectToken(Token::RBRACE); + token = selectToken(Token::RBrace); break; case '?': - token = selectToken(Token::CONDITIONAL); + token = selectToken(Token::Conditional); break; case '~': - token = selectToken(Token::BIT_NOT); + token = selectToken(Token::BitNot); break; default: if (isIdentifierStart(m_char)) @@ -545,17 +553,17 @@ void Scanner::scanToken() else if (isDecimalDigit(m_char)) token = scanNumber(); else if (skipWhitespace()) - token = Token::WHITESPACE; + token = Token::Whitespace; else if (isSourcePastEndOfInput()) token = Token::EOS; else - token = selectToken(Token::ILLEGAL); + token = selectToken(Token::Illegal); break; } // Continue scanning for tokens as long as we're just skipping // whitespace. } - while (token == Token::WHITESPACE); + while (token == Token::Whitespace); m_nextToken.location.end = getSourcePos(); m_nextToken.token = token; } @@ -613,16 +621,16 @@ Token::Value Scanner::scanString() if (c == '\\') { if (isSourcePastEndOfInput() || !scanEscape()) - return Token::ILLEGAL; + return Token::Illegal; } else addLiteralChar(c); } if (m_char != quote) - return Token::ILLEGAL; + return Token::Illegal; literal.complete(); advance(); // consume quote - return Token::STRING_LITERAL; + return Token::StringLiteral; } void Scanner::scanDecimalDigits() @@ -643,8 +651,7 @@ Token::Value Scanner::scanNumber(char _charSeen) } else { - if (_charSeen == '-') - addLiteralChar('-'); + solAssert(_charSeen == 0, ""); // if the first character is '0' we must check for octals and hex if (m_char == '0') { @@ -656,7 +663,7 @@ Token::Value Scanner::scanNumber(char _charSeen) kind = HEX; addLiteralCharAndAdvance(); if (!isHexDigit(m_char)) - return Token::ILLEGAL; // we must have at least one hex digit after 'x'/'X' + return Token::Illegal; // we must have at least one hex digit after 'x'/'X' while (isHexDigit(m_char)) addLiteralCharAndAdvance(); } @@ -677,13 +684,13 @@ Token::Value Scanner::scanNumber(char _charSeen) { solAssert(kind != HEX, "'e'/'E' must be scanned as part of the hex number"); if (kind != DECIMAL) - return Token::ILLEGAL; + return Token::Illegal; // scan exponent addLiteralCharAndAdvance(); if (m_char == '+' || m_char == '-') addLiteralCharAndAdvance(); if (!isDecimalDigit(m_char)) - return Token::ILLEGAL; // we must have at least one decimal digit after 'e'/'E' + return Token::Illegal; // we must have at least one decimal digit after 'e'/'E' scanDecimalDigits(); } // The source character immediately following a numeric literal must @@ -691,27 +698,9 @@ Token::Value Scanner::scanNumber(char _charSeen) // section 7.8.3, page 17 (note that we read only one decimal digit // if the value is 0). if (isDecimalDigit(m_char) || isIdentifierStart(m_char)) - return Token::ILLEGAL; + return Token::Illegal; literal.complete(); - return Token::NUMBER; -} - - -// ---------------------------------------------------------------------------- -// Keyword Matcher - - -static Token::Value keywordOrIdentifierToken(string const& _input) -{ - // The following macros are used inside TOKEN_LIST and cause non-keyword tokens to be ignored - // and keywords to be put inside the keywords variable. -#define KEYWORD(name, string, precedence) {string, Token::name}, -#define TOKEN(name, string, precedence) - static const map<string, Token::Value> keywords({TOKEN_LIST(TOKEN, KEYWORD)}); -#undef KEYWORD -#undef TOKEN - auto it = keywords.find(_input); - return it == keywords.end() ? Token::IDENTIFIER : it->second; + return Token::Number; } Token::Value Scanner::scanIdentifierOrKeyword() @@ -723,7 +712,7 @@ Token::Value Scanner::scanIdentifierOrKeyword() while (isIdentifierPart(m_char)) addLiteralCharAndAdvance(); literal.complete(); - return keywordOrIdentifierToken(m_nextToken.literal); + return Token::fromIdentifierOrKeyword(m_nextToken.literal); } char CharStream::advanceAndGet(size_t _chars) @@ -119,6 +119,7 @@ public: { return m_currentToken.token; } + Location getCurrentLocation() const { return m_currentToken.location; } std::string const& getCurrentLiteral() const { return m_currentToken.literal; } ///@} @@ -40,8 +40,11 @@ // You should have received a copy of the GNU General Public License // along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +#include <map> #include <libsolidity/Token.h> +using namespace std; + namespace dev { namespace solidity @@ -77,6 +80,19 @@ char const Token::m_tokenType[] = { TOKEN_LIST(KT, KK) }; +Token::Value Token::fromIdentifierOrKeyword(const std::string& _name) +{ + // The following macros are used inside TOKEN_LIST and cause non-keyword tokens to be ignored + // and keywords to be put inside the keywords variable. +#define KEYWORD(name, string, precedence) {string, Token::name}, +#define TOKEN(name, string, precedence) + static const map<string, Token::Value> keywords({TOKEN_LIST(TOKEN, KEYWORD)}); +#undef KEYWORD +#undef TOKEN + auto it = keywords.find(_name); + return it == keywords.end() ? Token::Identifier : it->second; +} + #undef KT #undef KK @@ -67,263 +67,274 @@ namespace solidity #define IGNORE_TOKEN(name, string, precedence) -#define TOKEN_LIST(T, K) \ - /* End of source indicator. */ \ - T(EOS, "EOS", 0) \ +#define TOKEN_LIST(T, K) \ + /* End of source indicator. */ \ + T(EOS, "EOS", 0) \ + \ + /* Punctuators (ECMA-262, section 7.7, page 15). */ \ + T(LParen, "(", 0) \ + T(RParen, ")", 0) \ + T(LBrack, "[", 0) \ + T(RBrack, "]", 0) \ + T(LBrace, "{", 0) \ + T(RBrace, "}", 0) \ + T(Colon, ":", 0) \ + T(Semicolon, ";", 0) \ + T(Period, ".", 0) \ + T(Conditional, "?", 3) \ + T(Arrow, "=>", 0) \ \ - /* Punctuators (ECMA-262, section 7.7, page 15). */ \ - T(LPAREN, "(", 0) \ - T(RPAREN, ")", 0) \ - T(LBRACK, "[", 0) \ - T(RBRACK, "]", 0) \ - T(LBRACE, "{", 0) \ - T(RBRACE, "}", 0) \ - T(COLON, ":", 0) \ - T(SEMICOLON, ";", 0) \ - T(PERIOD, ".", 0) \ - T(CONDITIONAL, "?", 3) \ - T(ARROW, "=>", 0) \ - \ - /* Assignment operators. */ \ - /* IsAssignmentOp() relies on this block of enum values being */ \ - /* contiguous and sorted in the same order!*/ \ - T(ASSIGN, "=", 2) \ + /* Assignment operators. */ \ + /* IsAssignmentOp() relies on this block of enum values being */ \ + /* contiguous and sorted in the same order!*/ \ + T(Assign, "=", 2) \ /* The following have to be in exactly the same order as the simple binary operators*/ \ - T(ASSIGN_BIT_OR, "|=", 2) \ - T(ASSIGN_BIT_XOR, "^=", 2) \ - T(ASSIGN_BIT_AND, "&=", 2) \ - T(ASSIGN_SHL, "<<=", 2) \ - T(ASSIGN_SAR, ">>=", 2) \ - T(ASSIGN_SHR, ">>>=", 2) \ - T(ASSIGN_ADD, "+=", 2) \ - T(ASSIGN_SUB, "-=", 2) \ - T(ASSIGN_MUL, "*=", 2) \ - T(ASSIGN_DIV, "/=", 2) \ - T(ASSIGN_MOD, "%=", 2) \ + T(AssignBitOr, "|=", 2) \ + T(AssignBitXor, "^=", 2) \ + T(AssignBitAnd, "&=", 2) \ + T(AssignShl, "<<=", 2) \ + T(AssignSar, ">>=", 2) \ + T(AssignShr, ">>>=", 2) \ + T(AssignAdd, "+=", 2) \ + T(AssignSub, "-=", 2) \ + T(AssignMul, "*=", 2) \ + T(AssignDiv, "/=", 2) \ + T(AssignMod, "%=", 2) \ \ /* Binary operators sorted by precedence. */ \ /* IsBinaryOp() relies on this block of enum values */ \ /* being contiguous and sorted in the same order! */ \ - T(COMMA, ",", 1) \ - T(OR, "||", 4) \ - T(AND, "&&", 5) \ - T(BIT_OR, "|", 8) \ - T(BIT_XOR, "^", 9) \ - T(BIT_AND, "&", 10) \ + T(Comma, ",", 1) \ + T(Or, "||", 4) \ + T(And, "&&", 5) \ + T(BitOr, "|", 8) \ + T(BitXor, "^", 9) \ + T(BitAnd, "&", 10) \ T(SHL, "<<", 11) \ T(SAR, ">>", 11) \ T(SHR, ">>>", 11) \ - T(ADD, "+", 12) \ - T(SUB, "-", 12) \ - T(MUL, "*", 13) \ - T(DIV, "/", 13) \ - T(MOD, "%", 13) \ + T(Add, "+", 12) \ + T(Sub, "-", 12) \ + T(Mul, "*", 13) \ + T(Div, "/", 13) \ + T(Mod, "%", 13) \ + T(Exp, "**", 14) \ \ /* Compare operators sorted by precedence. */ \ /* IsCompareOp() relies on this block of enum values */ \ /* being contiguous and sorted in the same order! */ \ - T(EQ, "==", 6) \ - T(NE, "!=", 6) \ - T(LT, "<", 7) \ - T(GT, ">", 7) \ - T(LTE, "<=", 7) \ - T(GTE, ">=", 7) \ - K(IN, "in", 7) \ + T(Equal, "==", 6) \ + T(NotEqual, "!=", 6) \ + T(LessThan, "<", 7) \ + T(GreaterThan, ">", 7) \ + T(LessThanOrEqual, "<=", 7) \ + T(GreaterThanOrEqual, ">=", 7) \ + K(In, "in", 7) \ \ /* Unary operators. */ \ /* IsUnaryOp() relies on this block of enum values */ \ /* being contiguous and sorted in the same order! */ \ - T(NOT, "!", 0) \ - T(BIT_NOT, "~", 0) \ - T(INC, "++", 0) \ - T(DEC, "--", 0) \ - K(DELETE, "delete", 0) \ + T(Not, "!", 0) \ + T(BitNot, "~", 0) \ + T(Inc, "++", 0) \ + T(Dec, "--", 0) \ + K(Delete, "delete", 0) \ \ /* Keywords */ \ - K(BREAK, "break", 0) \ - K(CASE, "case", 0) \ - K(CONST, "constant", 0) \ - K(CONTINUE, "continue", 0) \ - K(CONTRACT, "contract", 0) \ - K(DEFAULT, "default", 0) \ - K(DO, "do", 0) \ - K(ELSE, "else", 0) \ - K(EXTENDS, "extends", 0) \ - K(FOR, "for", 0) \ - K(FUNCTION, "function", 0) \ - K(IF, "if", 0) \ - K(IMPORT, "import", 0) \ - K(MAPPING, "mapping", 0) \ - K(NEW, "new", 0) \ - K(PUBLIC, "public", 0) \ - K(PRIVATE, "private", 0) \ - K(RETURN, "return", 0) \ - K(RETURNS, "returns", 0) \ - K(STRUCT, "struct", 0) \ - K(SWITCH, "switch", 0) \ - K(VAR, "var", 0) \ - K(WHILE, "while", 0) \ - \ + K(Break, "break", 0) \ + K(Case, "case", 0) \ + K(Const, "constant", 0) \ + K(Continue, "continue", 0) \ + K(Contract, "contract", 0) \ + K(Default, "default", 0) \ + K(Do, "do", 0) \ + K(Else, "else", 0) \ + K(Event, "event", 0) \ + K(External, "external", 0) \ + K(Is, "is", 0) \ + K(Indexed, "indexed", 0) \ + K(For, "for", 0) \ + K(Function, "function", 0) \ + K(If, "if", 0) \ + K(Import, "import", 0) \ + K(Mapping, "mapping", 0) \ + K(Modifier, "modifier", 0) \ + K(New, "new", 0) \ + K(Public, "public", 0) \ + K(Private, "private", 0) \ + K(Inheritable, "inheritable", 0) \ + K(Return, "return", 0) \ + K(Returns, "returns", 0) \ + K(Struct, "struct", 0) \ + K(Switch, "switch", 0) \ + K(Var, "var", 0) \ + K(While, "while", 0) \ + K(Enum, "enum", 0) \ \ + /* Ether subdenominations */ \ + K(SubWei, "wei", 0) \ + K(SubSzabo, "szabo", 0) \ + K(SubFinney, "finney", 0) \ + K(SubEther, "ether", 0) \ /* type keywords, keep them in this order, keep int as first keyword - * the implementation in Types.cpp has to be synced to this here - * TODO more to be added */ \ - K(INT, "int", 0) \ - K(INT8, "int8", 0) \ - K(INT16, "int16", 0) \ - K(INT24, "int24", 0) \ - K(INT32, "int32", 0) \ - K(INT40, "int40", 0) \ - K(INT48, "int48", 0) \ - K(INT56, "int56", 0) \ - K(INT64, "int64", 0) \ - K(INT72, "int72", 0) \ - K(INT80, "int80", 0) \ - K(INT88, "int88", 0) \ - K(INT96, "int96", 0) \ - K(INT104, "int104", 0) \ - K(INT112, "int112", 0) \ - K(INT120, "int120", 0) \ - K(INT128, "int128", 0) \ - K(INT136, "int136", 0) \ - K(INT144, "int144", 0) \ - K(INT152, "int152", 0) \ - K(INT160, "int160", 0) \ - K(INT168, "int168", 0) \ - K(INT176, "int178", 0) \ - K(INT184, "int184", 0) \ - K(INT192, "int192", 0) \ - K(INT200, "int200", 0) \ - K(INT208, "int208", 0) \ - K(INT216, "int216", 0) \ - K(INT224, "int224", 0) \ - K(INT232, "int232", 0) \ - K(INT240, "int240", 0) \ - K(INT248, "int248", 0) \ - K(INT256, "int256", 0) \ - K(UINT, "uint", 0) \ - K(UINT8, "uint8", 0) \ - K(UINT16, "uint16", 0) \ - K(UINT24, "uint24", 0) \ - K(UINT32, "uint32", 0) \ - K(UINT40, "uint40", 0) \ - K(UINT48, "uint48", 0) \ - K(UINT56, "uint56", 0) \ - K(UINT64, "uint64", 0) \ - K(UINT72, "uint72", 0) \ - K(UINT80, "uint80", 0) \ - K(UINT88, "uint88", 0) \ - K(UINT96, "uint96", 0) \ - K(UINT104, "uint104", 0) \ - K(UINT112, "uint112", 0) \ - K(UINT120, "uint120", 0) \ - K(UINT128, "uint128", 0) \ - K(UINT136, "uint136", 0) \ - K(UINT144, "uint144", 0) \ - K(UINT152, "uint152", 0) \ - K(UINT160, "uint160", 0) \ - K(UINT168, "uint168", 0) \ - K(UINT176, "uint178", 0) \ - K(UINT184, "uint184", 0) \ - K(UINT192, "uint192", 0) \ - K(UINT200, "uint200", 0) \ - K(UINT208, "uint208", 0) \ - K(UINT216, "uint216", 0) \ - K(UINT224, "uint224", 0) \ - K(UINT232, "uint232", 0) \ - K(UINT240, "uint240", 0) \ - K(UINT248, "uint248", 0) \ - K(UINT256, "uint256", 0) \ - K(HASH, "hash", 0) \ - K(HASH8, "hash8", 0) \ - K(HASH16, "hash16", 0) \ - K(HASH24, "hash24", 0) \ - K(HASH32, "hash32", 0) \ - K(HASH40, "hash40", 0) \ - K(HASH48, "hash48", 0) \ - K(HASH56, "hash56", 0) \ - K(HASH64, "hash64", 0) \ - K(HASH72, "hash72", 0) \ - K(HASH80, "hash80", 0) \ - K(HASH88, "hash88", 0) \ - K(HASH96, "hash96", 0) \ - K(HASH104, "hash104", 0) \ - K(HASH112, "hash112", 0) \ - K(HASH120, "hash120", 0) \ - K(HASH128, "hash128", 0) \ - K(HASH136, "hash136", 0) \ - K(HASH144, "hash144", 0) \ - K(HASH152, "hash152", 0) \ - K(HASH160, "hash160", 0) \ - K(HASH168, "hash168", 0) \ - K(HASH176, "hash178", 0) \ - K(HASH184, "hash184", 0) \ - K(HASH192, "hash192", 0) \ - K(HASH200, "hash200", 0) \ - K(HASH208, "hash208", 0) \ - K(HASH216, "hash216", 0) \ - K(HASH224, "hash224", 0) \ - K(HASH232, "hash232", 0) \ - K(HASH240, "hash240", 0) \ - K(HASH248, "hash248", 0) \ - K(HASH256, "hash256", 0) \ - K(ADDRESS, "address", 0) \ - K(BOOL, "bool", 0) \ - K(STRING_TYPE, "string", 0) \ - K(STRING0, "string0", 0) \ - K(STRING1, "string1", 0) \ - K(STRING2, "string2", 0) \ - K(STRING3, "string3", 0) \ - K(STRING4, "string4", 0) \ - K(STRING5, "string5", 0) \ - K(STRING6, "string6", 0) \ - K(STRING7, "string7", 0) \ - K(STRING8, "string8", 0) \ - K(STRING9, "string9", 0) \ - K(STRING10, "string10", 0) \ - K(STRING11, "string11", 0) \ - K(STRING12, "string12", 0) \ - K(STRING13, "string13", 0) \ - K(STRING14, "string14", 0) \ - K(STRING15, "string15", 0) \ - K(STRING16, "string16", 0) \ - K(STRING17, "string17", 0) \ - K(STRING18, "string18", 0) \ - K(STRING19, "string19", 0) \ - K(STRING20, "string20", 0) \ - K(STRING21, "string21", 0) \ - K(STRING22, "string22", 0) \ - K(STRING23, "string23", 0) \ - K(STRING24, "string24", 0) \ - K(STRING25, "string25", 0) \ - K(STRING26, "string26", 0) \ - K(STRING27, "string27", 0) \ - K(STRING28, "string28", 0) \ - K(STRING29, "string29", 0) \ - K(STRING30, "string30", 0) \ - K(STRING31, "string31", 0) \ - K(STRING32, "string32", 0) \ - K(TEXT, "text", 0) \ - K(REAL, "real", 0) \ - K(UREAL, "ureal", 0) \ - T(TYPES_END, NULL, 0) /* used as type enum end marker */ \ + * the implementation in Types.cpp has to be synced to this here */\ + K(Int, "int", 0) \ + K(Int8, "int8", 0) \ + K(Int16, "int16", 0) \ + K(Int24, "int24", 0) \ + K(Int32, "int32", 0) \ + K(Int40, "int40", 0) \ + K(Int48, "int48", 0) \ + K(Int56, "int56", 0) \ + K(Int64, "int64", 0) \ + K(Int72, "int72", 0) \ + K(Int80, "int80", 0) \ + K(Int88, "int88", 0) \ + K(Int96, "int96", 0) \ + K(Int104, "int104", 0) \ + K(Int112, "int112", 0) \ + K(Int120, "int120", 0) \ + K(Int128, "int128", 0) \ + K(Int136, "int136", 0) \ + K(Int144, "int144", 0) \ + K(Int152, "int152", 0) \ + K(Int160, "int160", 0) \ + K(Int168, "int168", 0) \ + K(Int176, "int178", 0) \ + K(Int184, "int184", 0) \ + K(Int192, "int192", 0) \ + K(Int200, "int200", 0) \ + K(Int208, "int208", 0) \ + K(Int216, "int216", 0) \ + K(Int224, "int224", 0) \ + K(Int232, "int232", 0) \ + K(Int240, "int240", 0) \ + K(Int248, "int248", 0) \ + K(Int256, "int256", 0) \ + K(UInt, "uint", 0) \ + K(UInt8, "uint8", 0) \ + K(UInt16, "uint16", 0) \ + K(UInt24, "uint24", 0) \ + K(UInt32, "uint32", 0) \ + K(UInt40, "uint40", 0) \ + K(UInt48, "uint48", 0) \ + K(UInt56, "uint56", 0) \ + K(UInt64, "uint64", 0) \ + K(UInt72, "uint72", 0) \ + K(UInt80, "uint80", 0) \ + K(UInt88, "uint88", 0) \ + K(UInt96, "uint96", 0) \ + K(UInt104, "uint104", 0) \ + K(UInt112, "uint112", 0) \ + K(UInt120, "uint120", 0) \ + K(UInt128, "uint128", 0) \ + K(UInt136, "uint136", 0) \ + K(UInt144, "uint144", 0) \ + K(UInt152, "uint152", 0) \ + K(UInt160, "uint160", 0) \ + K(UInt168, "uint168", 0) \ + K(UInt176, "uint178", 0) \ + K(UInt184, "uint184", 0) \ + K(UInt192, "uint192", 0) \ + K(UInt200, "uint200", 0) \ + K(UInt208, "uint208", 0) \ + K(UInt216, "uint216", 0) \ + K(UInt224, "uint224", 0) \ + K(UInt232, "uint232", 0) \ + K(UInt240, "uint240", 0) \ + K(UInt248, "uint248", 0) \ + K(UInt256, "uint256", 0) \ + K(Hash, "hash", 0) \ + K(Hash8, "hash8", 0) \ + K(Hash16, "hash16", 0) \ + K(Hash24, "hash24", 0) \ + K(Hash32, "hash32", 0) \ + K(Hash40, "hash40", 0) \ + K(Hash48, "hash48", 0) \ + K(Hash56, "hash56", 0) \ + K(Hash64, "hash64", 0) \ + K(Hash72, "hash72", 0) \ + K(Hash80, "hash80", 0) \ + K(Hash88, "hash88", 0) \ + K(Hash96, "hash96", 0) \ + K(Hash104, "hash104", 0) \ + K(Hash112, "hash112", 0) \ + K(Hash120, "hash120", 0) \ + K(Hash128, "hash128", 0) \ + K(Hash136, "hash136", 0) \ + K(Hash144, "hash144", 0) \ + K(Hash152, "hash152", 0) \ + K(Hash160, "hash160", 0) \ + K(Hash168, "hash168", 0) \ + K(Hash176, "hash178", 0) \ + K(Hash184, "hash184", 0) \ + K(Hash192, "hash192", 0) \ + K(Hash200, "hash200", 0) \ + K(Hash208, "hash208", 0) \ + K(Hash216, "hash216", 0) \ + K(Hash224, "hash224", 0) \ + K(Hash232, "hash232", 0) \ + K(Hash240, "hash240", 0) \ + K(Hash248, "hash248", 0) \ + K(Hash256, "hash256", 0) \ + K(Address, "address", 0) \ + K(Bool, "bool", 0) \ + K(Bytes, "bytes", 0) \ + K(StringType, "string", 0) \ + K(String0, "string0", 0) \ + K(String1, "string1", 0) \ + K(String2, "string2", 0) \ + K(String3, "string3", 0) \ + K(String4, "string4", 0) \ + K(String5, "string5", 0) \ + K(String6, "string6", 0) \ + K(String7, "string7", 0) \ + K(String8, "string8", 0) \ + K(String9, "string9", 0) \ + K(String10, "string10", 0) \ + K(String11, "string11", 0) \ + K(String12, "string12", 0) \ + K(String13, "string13", 0) \ + K(String14, "string14", 0) \ + K(String15, "string15", 0) \ + K(String16, "string16", 0) \ + K(String17, "string17", 0) \ + K(String18, "string18", 0) \ + K(String19, "string19", 0) \ + K(String20, "string20", 0) \ + K(String21, "string21", 0) \ + K(String22, "string22", 0) \ + K(String23, "string23", 0) \ + K(String24, "string24", 0) \ + K(String25, "string25", 0) \ + K(String26, "string26", 0) \ + K(String27, "string27", 0) \ + K(String28, "string28", 0) \ + K(String29, "string29", 0) \ + K(String30, "string30", 0) \ + K(String31, "string31", 0) \ + K(String32, "string32", 0) \ + K(Text, "text", 0) \ + K(Real, "real", 0) \ + K(UReal, "ureal", 0) \ + T(TypesEnd, NULL, 0) /* used as type enum end marker */ \ \ /* Literals */ \ - K(NULL_LITERAL, "null", 0) \ - K(TRUE_LITERAL, "true", 0) \ - K(FALSE_LITERAL, "false", 0) \ - T(NUMBER, NULL, 0) \ - T(STRING_LITERAL, NULL, 0) \ - T(COMMENT_LITERAL, NULL, 0) \ + K(NullLiteral, "null", 0) \ + K(TrueLiteral, "true", 0) \ + K(FalseLiteral, "false", 0) \ + T(Number, NULL, 0) \ + T(StringLiteral, NULL, 0) \ + T(CommentLiteral, NULL, 0) \ \ /* Identifiers (not keywords or future reserved words). */ \ - T(IDENTIFIER, NULL, 0) \ + T(Identifier, NULL, 0) \ \ /* Illegal token - not able to scan. */ \ - T(ILLEGAL, "ILLEGAL", 0) \ + T(Illegal, "ILLEGAL", 0) \ \ /* Scanner-internal use only. */ \ - T(WHITESPACE, NULL, 0) + T(Whitespace, NULL, 0) class Token @@ -350,24 +361,27 @@ public: } // Predicates - static bool isElementaryTypeName(Value tok) { return INT <= tok && tok < TYPES_END; } - static bool isAssignmentOp(Value tok) { return ASSIGN <= tok && tok <= ASSIGN_MOD; } - static bool isBinaryOp(Value op) { return COMMA <= op && op <= MOD; } - static bool isCommutativeOp(Value op) { return op == BIT_OR || op == BIT_XOR || op == BIT_AND || - op == ADD || op == MUL || op == EQ || op == NE; } - static bool isArithmeticOp(Value op) { return ADD <= op && op <= MOD; } - static bool isCompareOp(Value op) { return EQ <= op && op <= IN; } + static bool isElementaryTypeName(Value tok) { return Int <= tok && tok < TypesEnd; } + static bool isAssignmentOp(Value tok) { return Assign <= tok && tok <= AssignMod; } + static bool isBinaryOp(Value op) { return Comma <= op && op <= Exp; } + static bool isCommutativeOp(Value op) { return op == BitOr || op == BitXor || op == BitAnd || + op == Add || op == Mul || op == Equal || op == NotEqual; } + static bool isArithmeticOp(Value op) { return Add <= op && op <= Exp; } + static bool isCompareOp(Value op) { return Equal <= op && op <= In; } static Value AssignmentToBinaryOp(Value op) { - solAssert(isAssignmentOp(op) && op != ASSIGN, ""); - return Token::Value(op + (BIT_OR - ASSIGN_BIT_OR)); + solAssert(isAssignmentOp(op) && op != Assign, ""); + return Value(op + (BitOr - AssignBitOr)); } - static bool isBitOp(Value op) { return (BIT_OR <= op && op <= SHR) || op == BIT_NOT; } - static bool isUnaryOp(Value op) { return (NOT <= op && op <= DELETE) || op == ADD || op == SUB; } - static bool isCountOp(Value op) { return op == INC || op == DEC; } + static bool isBitOp(Value op) { return (BitOr <= op && op <= SHR) || op == BitNot; } + static bool isUnaryOp(Value op) { return (Not <= op && op <= Delete) || op == Add || op == Sub; } + static bool isCountOp(Value op) { return op == Inc || op == Dec; } static bool isShiftOp(Value op) { return (SHL <= op) && (op <= SHR); } + static bool isVisibilitySpecifier(Value op) { return isVariableVisibilitySpecifier(op) || op == External; } + static bool isVariableVisibilitySpecifier(Value op) { return op == Public || op == Private || op == Inheritable; } + static bool isEtherSubdenomination(Value op) { return op == SubWei || op == SubSzabo || op == SubFinney || op == Token::SubEther; } // Returns a string corresponding to the JS token string // (.e., "<" for the token LT) or NULL if the token doesn't @@ -386,6 +400,8 @@ public: return m_precedence[tok]; } + static Token::Value fromIdentifierOrKeyword(std::string const& _name); + private: static char const* const m_name[NUM_TOKENS]; static char const* const m_string[NUM_TOKENS]; @@ -26,6 +26,8 @@ #include <libsolidity/Types.h> #include <libsolidity/AST.h> +#include <limits> + using namespace std; namespace dev @@ -33,88 +35,94 @@ namespace dev namespace solidity { -shared_ptr<Type const> Type::fromElementaryTypeName(Token::Value _typeToken) +TypePointer Type::fromElementaryTypeName(Token::Value _typeToken) { - solAssert(Token::isElementaryTypeName(_typeToken), ""); + solAssert(Token::isElementaryTypeName(_typeToken), "Elementary type name expected."); - if (Token::INT <= _typeToken && _typeToken <= Token::HASH256) + if (Token::Int <= _typeToken && _typeToken <= Token::Hash256) { - int offset = _typeToken - Token::INT; + int offset = _typeToken - Token::Int; int bytes = offset % 33; if (bytes == 0) bytes = 32; int modifier = offset / 33; - return make_shared<IntegerType const>(bytes * 8, - modifier == 0 ? IntegerType::Modifier::SIGNED : - modifier == 1 ? IntegerType::Modifier::UNSIGNED : - IntegerType::Modifier::HASH); + return make_shared<IntegerType>(bytes * 8, + modifier == 0 ? IntegerType::Modifier::Signed : + modifier == 1 ? IntegerType::Modifier::Unsigned : + IntegerType::Modifier::Hash); } - else if (_typeToken == Token::ADDRESS) - return make_shared<IntegerType const>(0, IntegerType::Modifier::ADDRESS); - else if (_typeToken == Token::BOOL) - return make_shared<BoolType const>(); - else if (Token::STRING0 <= _typeToken && _typeToken <= Token::STRING32) - return make_shared<StaticStringType const>(int(_typeToken) - int(Token::STRING0)); + else if (_typeToken == Token::Address) + return make_shared<IntegerType>(0, IntegerType::Modifier::Address); + else if (_typeToken == Token::Bool) + return make_shared<BoolType>(); + else if (Token::String0 <= _typeToken && _typeToken <= Token::String32) + return make_shared<StaticStringType>(int(_typeToken) - int(Token::String0)); + else if (_typeToken == Token::Bytes) + return make_shared<ByteArrayType>(ByteArrayType::Location::Storage); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to convert elementary typename " + std::string(Token::toString(_typeToken)) + " to type.")); } -shared_ptr<Type const> Type::fromUserDefinedTypeName(UserDefinedTypeName const& _typeName) +TypePointer Type::fromElementaryTypeName(string const& _name) +{ + return fromElementaryTypeName(Token::fromIdentifierOrKeyword(_name)); +} + +TypePointer Type::fromUserDefinedTypeName(UserDefinedTypeName const& _typeName) { Declaration const* declaration = _typeName.getReferencedDeclaration(); if (StructDefinition const* structDef = dynamic_cast<StructDefinition const*>(declaration)) - return make_shared<StructType const>(*structDef); + return make_shared<StructType>(*structDef); + else if (EnumDefinition const* enumDef = dynamic_cast<EnumDefinition const*>(declaration)) + return make_shared<EnumType>(*enumDef); else if (FunctionDefinition const* function = dynamic_cast<FunctionDefinition const*>(declaration)) - return make_shared<FunctionType const>(*function); + return make_shared<FunctionType>(*function); else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration)) - return make_shared<ContractType const>(*contract); - return shared_ptr<Type const>(); + return make_shared<ContractType>(*contract); + return TypePointer(); } -shared_ptr<Type const> Type::fromMapping(Mapping const& _typeName) +TypePointer Type::fromMapping(Mapping const& _typeName) { - shared_ptr<Type const> keyType = _typeName.getKeyType().toType(); + TypePointer keyType = _typeName.getKeyType().toType(); if (!keyType) BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Error resolving type name.")); - shared_ptr<Type const> valueType = _typeName.getValueType().toType(); + TypePointer valueType = _typeName.getValueType().toType(); if (!valueType) BOOST_THROW_EXCEPTION(_typeName.getValueType().createTypeError("Invalid type name")); - return make_shared<MappingType const>(keyType, valueType); + return make_shared<MappingType>(keyType, valueType); } -shared_ptr<Type const> Type::forLiteral(Literal const& _literal) +TypePointer Type::forLiteral(Literal const& _literal) { switch (_literal.getToken()) { - case Token::TRUE_LITERAL: - case Token::FALSE_LITERAL: - return make_shared<BoolType const>(); - case Token::NUMBER: - return IntegerType::smallestTypeForLiteral(_literal.getValue()); - case Token::STRING_LITERAL: + case Token::TrueLiteral: + case Token::FalseLiteral: + return make_shared<BoolType>(); + case Token::Number: + return make_shared<IntegerConstantType>(_literal); + case Token::StringLiteral: //@todo put larger strings into dynamic strings return StaticStringType::smallestTypeForLiteral(_literal.getValue()); default: - return shared_ptr<Type const>(); + return shared_ptr<Type>(); } } -const MemberList Type::EmptyMemberList = MemberList(); - -shared_ptr<IntegerType const> IntegerType::smallestTypeForLiteral(string const& _literal) +TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b) { - bigint value(_literal); - bool isSigned = value < 0 || (!_literal.empty() && _literal.front() == '-'); - if (isSigned) - // convert to positive number of same bit requirements - value = ((-value) - 1) << 1; - unsigned bytes = max(bytesRequired(value), 1u); - if (bytes > 32) - return shared_ptr<IntegerType const>(); - return make_shared<IntegerType const>(bytes * 8, isSigned ? Modifier::SIGNED : Modifier::UNSIGNED); + if (_b->isImplicitlyConvertibleTo(*_a)) + return _a; + else if (_a->isImplicitlyConvertibleTo(*_b)) + return _b; + else + return TypePointer(); } +const MemberList Type::EmptyMemberList = MemberList(); + IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): m_bits(_bits), m_modifier(_modifier) { @@ -143,31 +151,36 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - return _convertTo.getCategory() == getCategory() || _convertTo.getCategory() == Category::CONTRACT; + if (_convertTo.getCategory() == Category::String) + { + StaticStringType const& convertTo = dynamic_cast<StaticStringType const&>(_convertTo); + return isHash() && (m_bits == convertTo.getNumBytes() * 8); + } + return _convertTo.getCategory() == getCategory() || + _convertTo.getCategory() == Category::Contract || + _convertTo.getCategory() == Category::Enum; } -bool IntegerType::acceptsBinaryOperator(Token::Value _operator) const +TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const { - if (isAddress()) - return Token::isCompareOp(_operator); + // "delete" is ok for all integer types + if (_operator == Token::Delete) + return make_shared<VoidType>(); + // no further unary operators for addresses + else if (isAddress()) + return TypePointer(); + // "~" is ok for all other types + else if (_operator == Token::BitNot) + return shared_from_this(); + // nothing else for hashes else if (isHash()) - return Token::isCompareOp(_operator) || Token::isBitOp(_operator); + return TypePointer(); + // for non-hash integers, we allow +, -, ++ and -- + else if (_operator == Token::Add || _operator == Token::Sub || + _operator == Token::Inc || _operator == Token::Dec) + return shared_from_this(); else - return true; -} - -bool IntegerType::acceptsUnaryOperator(Token::Value _operator) const -{ - if (_operator == Token::DELETE) - return true; - if (isAddress()) - return false; - if (_operator == Token::BIT_NOT) - return true; - if (isHash()) - return false; - return _operator == Token::ADD || _operator == Token::SUB || - _operator == Token::INC || _operator == Token::DEC; + return TypePointer(); } bool IntegerType::operator==(Type const& _other) const @@ -186,25 +199,202 @@ string IntegerType::toString() const return prefix + dev::toString(m_bits); } -u256 IntegerType::literalValue(Literal const& _literal) const +TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const { - bigint value(_literal.getValue()); - return u256(value); + if (_other->getCategory() != Category::IntegerConstant && _other->getCategory() != getCategory()) + return TypePointer(); + auto commonType = dynamic_pointer_cast<IntegerType const>(Type::commonType(shared_from_this(), _other)); + + if (!commonType) + return TypePointer(); + + // All integer types can be compared + if (Token::isCompareOp(_operator)) + return commonType; + + // Nothing else can be done with addresses, but hashes can receive bit operators + if (commonType->isAddress()) + return TypePointer(); + else if (commonType->isHash() && !Token::isBitOp(_operator)) + return TypePointer(); + else + return commonType; } const MemberList IntegerType::AddressMemberList = - MemberList({{"balance", - make_shared<IntegerType const>(256)}, - {"callstring32", - make_shared<FunctionType const>(TypePointers({make_shared<StaticStringType const>(32)}), - TypePointers(), FunctionType::Location::BARE)}, - {"callstring32string32", - make_shared<FunctionType const>(TypePointers({make_shared<StaticStringType const>(32), - make_shared<StaticStringType const>(32)}), - TypePointers(), FunctionType::Location::BARE)}, - {"send", - make_shared<FunctionType const>(TypePointers({make_shared<IntegerType const>(256)}), - TypePointers(), FunctionType::Location::SEND)}}); + MemberList({{"balance", make_shared<IntegerType >(256)}, + {"call", make_shared<FunctionType>(strings(), strings(), FunctionType::Location::Bare, true)}, + {"send", make_shared<FunctionType>(strings{"uint"}, strings{}, FunctionType::Location::Send)}}); + +IntegerConstantType::IntegerConstantType(Literal const& _literal) +{ + m_value = bigint(_literal.getValue()); + + switch (_literal.getSubDenomination()) + { + case Literal::SubDenomination::Wei: + case Literal::SubDenomination::None: + break; + case Literal::SubDenomination::Szabo: + m_value *= bigint("1000000000000"); + break; + case Literal::SubDenomination::Finney: + m_value *= bigint("1000000000000000"); + break; + case Literal::SubDenomination::Ether: + m_value *= bigint("1000000000000000000"); + break; + } +} + +bool IntegerConstantType::isImplicitlyConvertibleTo(Type const& _convertTo) const +{ + TypePointer integerType = getIntegerType(); + return integerType && integerType->isImplicitlyConvertibleTo(_convertTo); +} + +bool IntegerConstantType::isExplicitlyConvertibleTo(Type const& _convertTo) const +{ + TypePointer integerType = getIntegerType(); + return integerType && integerType->isExplicitlyConvertibleTo(_convertTo); +} + +TypePointer IntegerConstantType::unaryOperatorResult(Token::Value _operator) const +{ + bigint value; + switch (_operator) + { + case Token::BitNot: + value = ~m_value; + break; + case Token::Add: + value = m_value; + break; + case Token::Sub: + value = -m_value; + break; + default: + return TypePointer(); + } + return make_shared<IntegerConstantType>(value); +} + +TypePointer IntegerConstantType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const +{ + if (_other->getCategory() == Category::Integer) + { + shared_ptr<IntegerType const> integerType = getIntegerType(); + if (!integerType) + return TypePointer(); + return integerType->binaryOperatorResult(_operator, _other); + } + else if (_other->getCategory() != getCategory()) + return TypePointer(); + + IntegerConstantType const& other = dynamic_cast<IntegerConstantType const&>(*_other); + if (Token::isCompareOp(_operator)) + { + shared_ptr<IntegerType const> thisIntegerType = getIntegerType(); + shared_ptr<IntegerType const> otherIntegerType = other.getIntegerType(); + if (!thisIntegerType || !otherIntegerType) + return TypePointer(); + return thisIntegerType->binaryOperatorResult(_operator, otherIntegerType); + } + else + { + bigint value; + switch (_operator) + { + case Token::BitOr: + value = m_value | other.m_value; + break; + case Token::BitXor: + value = m_value ^ other.m_value; + break; + case Token::BitAnd: + value = m_value & other.m_value; + break; + case Token::Add: + value = m_value + other.m_value; + break; + case Token::Sub: + value = m_value - other.m_value; + break; + case Token::Mul: + value = m_value * other.m_value; + break; + case Token::Div: + if (other.m_value == 0) + return TypePointer(); + value = m_value / other.m_value; + break; + case Token::Mod: + if (other.m_value == 0) + return TypePointer(); + value = m_value % other.m_value; + break; + case Token::Exp: + if (other.m_value < 0) + return TypePointer(); + else if (other.m_value > std::numeric_limits<unsigned int>::max()) + return TypePointer(); + else + value = boost::multiprecision::pow(m_value, other.m_value.convert_to<unsigned int>()); + break; + default: + return TypePointer(); + } + return make_shared<IntegerConstantType>(value); + } +} + +bool IntegerConstantType::operator==(Type const& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + return m_value == dynamic_cast<IntegerConstantType const&>(_other).m_value; +} + +string IntegerConstantType::toString() const +{ + return "int_const " + m_value.str(); +} + +u256 IntegerConstantType::literalValue(Literal const*) const +{ + u256 value; + // we ignore the literal and hope that the type was correctly determined + solAssert(m_value <= u256(-1), "Integer constant too large."); + solAssert(m_value >= -(bigint(1) << 255), "Integer constant too small."); + + if (m_value >= 0) + value = u256(m_value); + else + value = s2u(s256(m_value)); + + return value; +} + +TypePointer IntegerConstantType::getRealType() const +{ + auto intType = getIntegerType(); + solAssert(!!intType, "getRealType called with invalid integer constant " + toString()); + return intType; +} + +shared_ptr<IntegerType const> IntegerConstantType::getIntegerType() const +{ + bigint value = m_value; + bool negative = (value < 0); + if (negative) // convert to positive number of same bit requirements + value = ((-value) - 1) << 1; + if (value > u256(-1)) + return shared_ptr<IntegerType const>(); + else + return make_shared<IntegerType>(max(bytesRequired(value), 1u) * 8, + negative ? IntegerType::Modifier::Signed + : IntegerType::Modifier::Unsigned); +} shared_ptr<StaticStringType> StaticStringType::smallestTypeForLiteral(string const& _literal) { @@ -227,6 +417,20 @@ bool StaticStringType::isImplicitlyConvertibleTo(Type const& _convertTo) const return convertTo.m_bytes >= m_bytes; } +bool StaticStringType::isExplicitlyConvertibleTo(Type const& _convertTo) const +{ + if (_convertTo.getCategory() == getCategory()) + return true; + if (_convertTo.getCategory() == Category::Integer) + { + IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo); + if (convertTo.isHash() && (m_bytes * 8 == convertTo.getNumBits())) + return true; + } + + return false; +} + bool StaticStringType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) @@ -235,12 +439,13 @@ bool StaticStringType::operator==(Type const& _other) const return other.m_bytes == m_bytes; } -u256 StaticStringType::literalValue(const Literal& _literal) const +u256 StaticStringType::literalValue(const Literal* _literal) const { + solAssert(_literal, ""); u256 value = 0; - for (char c: _literal.getValue()) + for (char c: _literal->getValue()) value = (value << 8) | byte(c); - return value << ((32 - _literal.getValue().length()) * 8); + return value << ((32 - _literal->getValue().length()) * 8); } bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const @@ -256,44 +461,110 @@ bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const return isImplicitlyConvertibleTo(_convertTo); } -u256 BoolType::literalValue(Literal const& _literal) const +u256 BoolType::literalValue(Literal const* _literal) const { - if (_literal.getToken() == Token::TRUE_LITERAL) + solAssert(_literal, ""); + if (_literal->getToken() == Token::TrueLiteral) return u256(1); - else if (_literal.getToken() == Token::FALSE_LITERAL) + else if (_literal->getToken() == Token::FalseLiteral) return u256(0); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Bool type constructed from non-boolean literal.")); } -bool ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const +TypePointer BoolType::unaryOperatorResult(Token::Value _operator) const +{ + if (_operator == Token::Delete) + return make_shared<VoidType>(); + return (_operator == Token::Not) ? shared_from_this() : TypePointer(); +} + +TypePointer BoolType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const { - if (isImplicitlyConvertibleTo(_convertTo)) + if (getCategory() != _other->getCategory()) + return TypePointer(); + if (Token::isCompareOp(_operator) || _operator == Token::And || _operator == Token::Or) + return _other; + else + return TypePointer(); +} + +bool ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const +{ + if (*this == _convertTo) return true; - if (_convertTo.getCategory() == Category::INTEGER) + if (_convertTo.getCategory() == Category::Integer) return dynamic_cast<IntegerType const&>(_convertTo).isAddress(); + if (_convertTo.getCategory() == Category::Contract) + { + auto const& bases = getContractDefinition().getLinearizedBaseContracts(); + if (m_super && bases.size() <= 1) + return false; + return find(m_super ? ++bases.begin() : bases.begin(), bases.end(), + &dynamic_cast<ContractType const&>(_convertTo).getContractDefinition()) != bases.end(); + } return false; } -bool ContractType::operator==(Type const& _other) const +bool ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const +{ + return isImplicitlyConvertibleTo(_convertTo) || _convertTo.getCategory() == Category::Integer || + _convertTo.getCategory() == Category::Contract; +} + +TypePointer ContractType::unaryOperatorResult(Token::Value _operator) const +{ + return _operator == Token::Delete ? make_shared<VoidType>() : TypePointer(); +} + +bool ByteArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const +{ + return _convertTo.getCategory() == getCategory(); +} + +TypePointer ByteArrayType::unaryOperatorResult(Token::Value _operator) const +{ + if (_operator == Token::Delete) + return make_shared<VoidType>(); + return TypePointer(); +} + +bool ByteArrayType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) return false; - ContractType const& other = dynamic_cast<ContractType const&>(_other); - return other.m_contract == m_contract; + ByteArrayType const& other = dynamic_cast<ByteArrayType const&>(_other); + return other.m_location == m_location; } -u256 ContractType::getStorageSize() const +unsigned ByteArrayType::getSizeOnStack() const { - u256 size = 0; - for (ASTPointer<VariableDeclaration> const& variable: m_contract.getStateVariables()) - size += variable->getType()->getStorageSize(); - return max<u256>(1, size); + if (m_location == Location::CallData) + // offset, length (stack top) + return 2; + else + // offset + return 1; +} + +shared_ptr<ByteArrayType> ByteArrayType::copyForLocation(ByteArrayType::Location _location) const +{ + return make_shared<ByteArrayType>(_location); +} + +const MemberList ByteArrayType::s_byteArrayMemberList = MemberList({{"length", make_shared<IntegerType>(256)}}); + +bool ContractType::operator==(Type const& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + ContractType const& other = dynamic_cast<ContractType const&>(_other); + return other.m_contract == m_contract && other.m_super == m_super; } string ContractType::toString() const { - return "contract " + m_contract.getName(); + return "contract " + string(m_super ? "super " : "") + m_contract.getName(); } MemberList const& ContractType::getMembers() const @@ -301,9 +572,20 @@ MemberList const& ContractType::getMembers() const // We need to lazy-initialize it because of recursive references. if (!m_members) { - map<string, shared_ptr<Type const>> members; - for (FunctionDefinition const* function: m_contract.getInterfaceFunctions()) - members[function->getName()] = make_shared<FunctionType>(*function, false); + // All address members and all interface functions + vector<pair<string, TypePointer>> members(IntegerType::AddressMemberList.begin(), + IntegerType::AddressMemberList.end()); + if (m_super) + { + for (ContractDefinition const* base: m_contract.getLinearizedBaseContracts()) + for (ASTPointer<FunctionDefinition> const& function: base->getDefinedFunctions()) + if (!function->isConstructor() && !function->getName().empty()&& + function->isVisibleInDerivedContracts()) + members.push_back(make_pair(function->getName(), make_shared<FunctionType>(*function, true))); + } + else + for (auto const& it: m_contract.getInterfaceFunctions()) + members.push_back(make_pair(it.second->getDeclaration().getName(), it.second)); m_members.reset(new MemberList(members)); } return *m_members; @@ -315,23 +597,26 @@ shared_ptr<FunctionType const> const& ContractType::getConstructorType() const { FunctionDefinition const* constructor = m_contract.getConstructor(); if (constructor) - m_constructorType = make_shared<FunctionType const>(*constructor); + m_constructorType = make_shared<FunctionType>(*constructor); else - m_constructorType = make_shared<FunctionType const>(TypePointers(), TypePointers()); + m_constructorType = make_shared<FunctionType>(TypePointers(), TypePointers()); } return m_constructorType; } -unsigned ContractType::getFunctionIndex(string const& _functionName) const +u256 ContractType::getFunctionIdentifier(string const& _functionName) const { - unsigned index = 0; - for (FunctionDefinition const* function: m_contract.getInterfaceFunctions()) - { - if (function->getName() == _functionName) - return index; - ++index; - } - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Index of non-existing contract function requested.")); + auto interfaceFunctions = m_contract.getInterfaceFunctions(); + for (auto const& it: m_contract.getInterfaceFunctions()) + if (it.second->getDeclaration().getName() == _functionName) + return FixedHash<4>::Arith(it.first); + + return Invalid256; +} + +TypePointer StructType::unaryOperatorResult(Token::Value _operator) const +{ + return _operator == Token::Delete ? make_shared<VoidType>() : TypePointer(); } bool StructType::operator==(Type const& _other) const @@ -345,14 +630,14 @@ bool StructType::operator==(Type const& _other) const u256 StructType::getStorageSize() const { u256 size = 0; - for (pair<string, shared_ptr<Type const>> const& member: getMembers()) + for (pair<string, TypePointer> const& member: getMembers()) size += member.second->getStorageSize(); return max<u256>(1, size); } bool StructType::canLiveOutsideStorage() const { - for (pair<string, shared_ptr<Type const>> const& member: getMembers()) + for (pair<string, TypePointer> const& member: getMembers()) if (!member.second->canLiveOutsideStorage()) return false; return true; @@ -368,9 +653,9 @@ MemberList const& StructType::getMembers() const // We need to lazy-initialize it because of recursive references. if (!m_members) { - map<string, shared_ptr<Type const>> members; + vector<pair<string, TypePointer>> members; for (ASTPointer<VariableDeclaration> const& variable: m_struct.getMembers()) - members[variable->getName()] = variable->getType(); + members.push_back(make_pair(variable->getName(), variable->getType())); m_members.reset(new MemberList(members)); } return *m_members; @@ -380,7 +665,7 @@ u256 StructType::getStorageOffsetOfMember(string const& _name) const { //@todo cache member offset? u256 offset; - for (ASTPointer<VariableDeclaration> variable: m_struct.getMembers()) + for (ASTPointer<VariableDeclaration> const& variable: m_struct.getMembers()) { if (variable->getName() == _name) return offset; @@ -389,19 +674,122 @@ u256 StructType::getStorageOffsetOfMember(string const& _name) const BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage offset of non-existing member requested.")); } -FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal) +TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const +{ + return _operator == Token::Delete ? make_shared<VoidType>() : TypePointer(); +} + +bool EnumType::operator==(Type const& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + EnumType const& other = dynamic_cast<EnumType const&>(_other); + return other.m_enum == m_enum; +} + +string EnumType::toString() const +{ + return string("enum ") + m_enum.getName(); +} + +bool EnumType::isExplicitlyConvertibleTo(Type const& _convertTo) const +{ + return _convertTo.getCategory() == getCategory() || _convertTo.getCategory() == Category::Integer; +} + +unsigned int EnumType::getMemberValue(ASTString const& _member) const +{ + unsigned int index = 0; + for (ASTPointer<EnumValue> const& decl: m_enum.getMembers()) + { + if (decl->getName() == _member) + return index; + ++index; + } + BOOST_THROW_EXCEPTION(m_enum.createTypeError("Requested unknown enum value ." + _member)); +} + +FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal): + m_location(_isInternal ? Location::Internal : Location::External), + m_isConstant(_function.isDeclaredConst()), + m_declaration(&_function) { TypePointers params; + vector<string> paramNames; TypePointers retParams; + vector<string> retParamNames; + params.reserve(_function.getParameters().size()); + paramNames.reserve(_function.getParameters().size()); for (ASTPointer<VariableDeclaration> const& var: _function.getParameters()) + { + paramNames.push_back(var->getName()); params.push_back(var->getType()); + } retParams.reserve(_function.getReturnParameters().size()); + retParamNames.reserve(_function.getReturnParameters().size()); for (ASTPointer<VariableDeclaration> const& var: _function.getReturnParameters()) + { + retParamNames.push_back(var->getName()); retParams.push_back(var->getType()); + } swap(params, m_parameterTypes); + swap(paramNames, m_parameterNames); swap(retParams, m_returnParameterTypes); - m_location = _isInternal ? Location::INTERNAL : Location::EXTERNAL; + swap(retParamNames, m_returnParameterNames); +} + +FunctionType::FunctionType(VariableDeclaration const& _varDecl): + m_location(Location::External), m_isConstant(true), m_declaration(&_varDecl) +{ + TypePointers params; + vector<string> paramNames; + auto returnType = _varDecl.getType(); + + while (auto mappingType = dynamic_cast<MappingType const*>(returnType.get())) + { + params.push_back(mappingType->getKeyType()); + paramNames.push_back(""); + returnType = mappingType->getValueType(); + } + + TypePointers retParams; + vector<string> retParamNames; + if (auto structType = dynamic_cast<StructType const*>(returnType.get())) + { + for (pair<string, TypePointer> const& member: structType->getMembers()) + if (member.second->canLiveOutsideStorage()) + { + retParamNames.push_back(member.first); + retParams.push_back(member.second); + } + } + else + { + retParams.push_back(returnType); + retParamNames.push_back(""); + } + + swap(params, m_parameterTypes); + swap(paramNames, m_parameterNames); + swap(retParams, m_returnParameterTypes); + swap(retParamNames, m_returnParameterNames); +} + +FunctionType::FunctionType(const EventDefinition& _event): + m_location(Location::Event), m_isConstant(true), m_declaration(&_event) +{ + TypePointers params; + vector<string> paramNames; + params.reserve(_event.getParameters().size()); + paramNames.reserve(_event.getParameters().size()); + for (ASTPointer<VariableDeclaration> const& var: _event.getParameters()) + { + paramNames.push_back(var->getName()); + params.push_back(var->getType()); + } + swap(params, m_parameterTypes); + swap(paramNames, m_parameterNames); } bool FunctionType::operator==(Type const& _other) const @@ -412,6 +800,9 @@ bool FunctionType::operator==(Type const& _other) const if (m_location != other.m_location) return false; + if (m_isConstant != other.isConstant()) + return false; + if (m_parameterTypes.size() != other.m_parameterTypes.size() || m_returnParameterTypes.size() != other.m_returnParameterTypes.size()) return false; @@ -423,6 +814,9 @@ bool FunctionType::operator==(Type const& _other) const if (!equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(), other.m_returnParameterTypes.cbegin(), typeCompare)) return false; + //@todo this is ugly, but cannot be prevented right now + if (m_gasSet != other.m_gasSet || m_valueSet != other.m_valueSet) + return false; return true; } @@ -439,19 +833,106 @@ string FunctionType::toString() const unsigned FunctionType::getSizeOnStack() const { + unsigned size = 0; + if (m_location == Location::External) + size = 2; + else if (m_location == Location::Internal || m_location == Location::Bare) + size = 1; + if (m_gasSet) + size++; + if (m_valueSet) + size++; + return size; +} + +MemberList const& FunctionType::getMembers() const +{ switch (m_location) { - case Location::INTERNAL: - return 1; - case Location::EXTERNAL: - return 2; - case Location::BARE: - return 1; + case Location::External: + case Location::Creation: + case Location::ECRecover: + case Location::SHA256: + case Location::RIPEMD160: + case Location::Bare: + if (!m_members) + { + vector<pair<string, TypePointer>> members{ + {"value", make_shared<FunctionType>(parseElementaryTypeVector({"uint"}), + TypePointers{copyAndSetGasOrValue(false, true)}, + Location::SetValue, false, m_gasSet, m_valueSet)}}; + if (m_location != Location::Creation) + members.push_back(make_pair("gas", make_shared<FunctionType>( + parseElementaryTypeVector({"uint"}), + TypePointers{copyAndSetGasOrValue(true, false)}, + Location::SetGas, false, m_gasSet, m_valueSet))); + m_members.reset(new MemberList(members)); + } + return *m_members; default: - return 0; + return EmptyMemberList; } } +string FunctionType::getCanonicalSignature(std::string const& _name) const +{ + std::string funcName = _name; + if (_name == "") + { + solAssert(m_declaration != nullptr, "Function type without name needs a declaration"); + funcName = m_declaration->getName(); + } + string ret = funcName + "("; + + for (auto it = m_parameterTypes.cbegin(); it != m_parameterTypes.cend(); ++it) + ret += (*it)->toString() + (it + 1 == m_parameterTypes.cend() ? "" : ","); + + return ret + ")"; +} + +TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) +{ + TypePointers pointers; + pointers.reserve(_types.size()); + for (string const& type: _types) + pointers.push_back(Type::fromElementaryTypeName(type)); + return pointers; +} + +TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) const +{ + return make_shared<FunctionType>(m_parameterTypes, m_returnParameterTypes, m_location, + m_arbitraryParameters, + m_gasSet || _setGas, m_valueSet || _setValue); +} + +vector<string> const FunctionType::getParameterTypeNames() const +{ + vector<string> names; + for (TypePointer const& t: m_parameterTypes) + names.push_back(t->toString()); + + return names; +} + +vector<string> const FunctionType::getReturnParameterTypeNames() const +{ + vector<string> names; + for (TypePointer const& t: m_returnParameterTypes) + names.push_back(t->toString()); + + return names; +} + +ASTPointer<ASTString> FunctionType::getDocumentation() const +{ + auto function = dynamic_cast<Documented const*>(m_declaration); + if (function) + return function->getDocumentation(); + + return ASTPointer<ASTString>(); +} + bool MappingType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) @@ -473,27 +954,90 @@ bool TypeType::operator==(Type const& _other) const return *getActualType() == *other.getActualType(); } +MemberList const& TypeType::getMembers() const +{ + // We need to lazy-initialize it because of recursive references. + if (!m_members) + { + vector<pair<string, TypePointer>> members; + if (m_actualType->getCategory() == Category::Contract && m_currentContract != nullptr) + { + ContractDefinition const& contract = dynamic_cast<ContractType const&>(*m_actualType).getContractDefinition(); + vector<ContractDefinition const*> currentBases = m_currentContract->getLinearizedBaseContracts(); + if (find(currentBases.begin(), currentBases.end(), &contract) != currentBases.end()) + // We are accessing the type of a base contract, so add all public and protected + // functions. Note that this does not add inherited functions on purpose. + for (ASTPointer<FunctionDefinition> const& f: contract.getDefinedFunctions()) + if (!f->isConstructor() && !f->getName().empty() && f->isVisibleInDerivedContracts()) + members.push_back(make_pair(f->getName(), make_shared<FunctionType>(*f))); + } + else if (m_actualType->getCategory() == Category::Enum) + { + EnumDefinition const& enumDef = dynamic_cast<EnumType const&>(*m_actualType).getEnumDefinition(); + auto enumType = make_shared<EnumType>(enumDef); + for (ASTPointer<EnumValue> const& enumValue: enumDef.getMembers()) + members.push_back(make_pair(enumValue->getName(), enumType)); + } + m_members.reset(new MemberList(members)); + } + return *m_members; +} + +ModifierType::ModifierType(const ModifierDefinition& _modifier) +{ + TypePointers params; + params.reserve(_modifier.getParameters().size()); + for (ASTPointer<VariableDeclaration> const& var: _modifier.getParameters()) + params.push_back(var->getType()); + swap(params, m_parameterTypes); +} + +bool ModifierType::operator==(Type const& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + ModifierType const& other = dynamic_cast<ModifierType const&>(_other); + + if (m_parameterTypes.size() != other.m_parameterTypes.size()) + return false; + auto typeCompare = [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; }; + + if (!equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(), + other.m_parameterTypes.cbegin(), typeCompare)) + return false; + return true; +} + +string ModifierType::toString() const +{ + string name = "modifier ("; + for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it) + name += (*it)->toString() + (it + 1 == m_parameterTypes.end() ? "" : ","); + return name + ")"; +} + MagicType::MagicType(MagicType::Kind _kind): m_kind(_kind) { switch (m_kind) { - case Kind::BLOCK: - m_members = MemberList({{"coinbase", make_shared<IntegerType const>(0, IntegerType::Modifier::ADDRESS)}, - {"timestamp", make_shared<IntegerType const>(256)}, - {"prevhash", make_shared<IntegerType const>(256, IntegerType::Modifier::HASH)}, - {"difficulty", make_shared<IntegerType const>(256)}, - {"number", make_shared<IntegerType const>(256)}, - {"gaslimit", make_shared<IntegerType const>(256)}}); + case Kind::Block: + m_members = MemberList({{"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, + {"timestamp", make_shared<IntegerType>(256)}, + {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"hash"}, FunctionType::Location::BlockHash)}, + {"difficulty", make_shared<IntegerType>(256)}, + {"number", make_shared<IntegerType>(256)}, + {"gaslimit", make_shared<IntegerType>(256)}}); break; - case Kind::MSG: - m_members = MemberList({{"sender", make_shared<IntegerType const>(0, IntegerType::Modifier::ADDRESS)}, - {"gas", make_shared<IntegerType const>(256)}, - {"value", make_shared<IntegerType const>(256)}}); + case Kind::Message: + m_members = MemberList({{"sender", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, + {"gas", make_shared<IntegerType>(256)}, + {"value", make_shared<IntegerType>(256)}, + {"data", make_shared<ByteArrayType>(ByteArrayType::Location::CallData)}}); break; - case Kind::TX: - m_members = MemberList({{"origin", make_shared<IntegerType const>(0, IntegerType::Modifier::ADDRESS)}, - {"gasprice", make_shared<IntegerType const>(256)}}); + case Kind::Transaction: + m_members = MemberList({{"origin", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, + {"gasprice", make_shared<IntegerType>(256)}}); break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of magic.")); @@ -512,11 +1056,11 @@ string MagicType::toString() const { switch (m_kind) { - case Kind::BLOCK: + case Kind::Block: return "block"; - case Kind::MSG: + case Kind::Message: return "msg"; - case Kind::TX: + case Kind::Transaction: return "tx"; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of magic.")); @@ -41,6 +41,7 @@ namespace solidity class Type; // forward class FunctionType; // forward using TypePointer = std::shared_ptr<Type const>; +using FunctionTypePointer = std::shared_ptr<FunctionType const>; using TypePointers = std::vector<TypePointer>; /** @@ -49,14 +50,16 @@ using TypePointers = std::vector<TypePointer>; class MemberList { public: - using MemberMap = std::map<std::string, TypePointer>; + using MemberMap = std::vector<std::pair<std::string, TypePointer>>; MemberList() {} explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {} TypePointer getMemberType(std::string const& _name) const { - auto it = m_memberTypes.find(_name); - return it != m_memberTypes.end() ? it->second : TypePointer(); + for (auto const& it: m_memberTypes) + if (it.first == _name) + return it.second; + return TypePointer(); } MemberMap::const_iterator begin() const { return m_memberTypes.begin(); } @@ -70,26 +73,31 @@ private: /** * Abstract base class that forms the root of the type hierarchy. */ -class Type: private boost::noncopyable +class Type: private boost::noncopyable, public std::enable_shared_from_this<Type> { public: enum class Category { - INTEGER, BOOL, REAL, STRING, CONTRACT, STRUCT, FUNCTION, MAPPING, VOID, TYPE, MAGIC + Integer, IntegerConstant, Bool, Real, ByteArray, + String, Contract, Struct, Function, Enum, + Mapping, Void, TypeType, Modifier, Magic }; ///@{ ///@name Factory functions /// Factory functions that convert an AST @ref TypeName to a Type. - static std::shared_ptr<Type const> fromElementaryTypeName(Token::Value _typeToken); - static std::shared_ptr<Type const> fromUserDefinedTypeName(UserDefinedTypeName const& _typeName); - static std::shared_ptr<Type const> fromMapping(Mapping const& _typeName); - static std::shared_ptr<Type const> fromFunction(FunctionDefinition const& _function); + static TypePointer fromElementaryTypeName(Token::Value _typeToken); + static TypePointer fromElementaryTypeName(std::string const& _name); + static TypePointer fromUserDefinedTypeName(UserDefinedTypeName const& _typeName); + static TypePointer fromMapping(Mapping const& _typeName); + static TypePointer fromFunction(FunctionDefinition const& _function); /// @} /// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does /// not fit any type. - static std::shared_ptr<Type const> forLiteral(Literal const& _literal); + static TypePointer forLiteral(Literal const& _literal); + /// @returns a pointer to _a or _b if the other is implicitly convertible to it or nullptr otherwise + static TypePointer commonType(TypePointer const& _a, TypePointer const& _b); virtual Category getCategory() const = 0; virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } @@ -97,15 +105,27 @@ public: { return isImplicitlyConvertibleTo(_convertTo); } - virtual bool acceptsBinaryOperator(Token::Value) const { return false; } - virtual bool acceptsUnaryOperator(Token::Value) const { return false; } + /// @returns the resulting type of applying the given unary operator or an empty pointer if + /// this is not possible. + /// The default implementation does not allow any unary operator. + virtual TypePointer unaryOperatorResult(Token::Value) const { return TypePointer(); } + /// @returns the resulting type of applying the given binary operator or an empty pointer if + /// this is not possible. + /// The default implementation allows comparison operators if a common type exists + virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const + { + return Token::isCompareOp(_operator) ? commonType(shared_from_this(), _other) : TypePointer(); + } virtual bool operator==(Type const& _other) const { return getCategory() == _other.getCategory(); } virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); } /// @returns number of bytes used by this type when encoded for CALL, or 0 if the encoding - /// is not a simple big-endian encoding or the type cannot be stored on the stack. + /// is not a simple big-endian encoding or the type cannot be stored in calldata. + /// Note that irrespective of this size, each calldata element is padded to a multiple of 32 bytes. virtual unsigned getCalldataEncodedSize() const { return 0; } + /// @returns true if the type is dynamically encoded in calldata + virtual bool isDynamicallySized() const { return false; } /// @returns number of bytes required to hold this value in storage. /// For dynamically "allocated" types, it returns the size of the statically allocated head, virtual u256 getStorageSize() const { return 1; } @@ -117,6 +137,8 @@ public: /// i.e. it behaves differently in lvalue context and in value context. virtual bool isValueType() const { return false; } virtual unsigned getSizeOnStack() const { return 1; } + /// @returns the real type of some types, like e.g: IntegerConstant + virtual TypePointer getRealType() const { return shared_from_this(); } /// Returns the list of all members of this type. Default implementation: no members. virtual MemberList const& getMembers() const { return EmptyMemberList; } @@ -124,10 +146,10 @@ public: TypePointer getMemberType(std::string const& _name) const { return getMembers().getMemberType(_name); } virtual std::string toString() const = 0; - virtual u256 literalValue(Literal const&) const + virtual u256 literalValue(Literal const*) const { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Literal value requested " - "for type without literals.")); + "for type without literals.")); } protected: @@ -143,20 +165,16 @@ class IntegerType: public Type public: enum class Modifier { - UNSIGNED, SIGNED, HASH, ADDRESS + Unsigned, Signed, Hash, Address }; - virtual Category getCategory() const override { return Category::INTEGER; } - - /// @returns the smallest integer type for the given literal or an empty pointer - /// if no type fits. - static std::shared_ptr<IntegerType const> smallestTypeForLiteral(std::string const& _literal); + virtual Category getCategory() const override { return Category::Integer; } - explicit IntegerType(int _bits, Modifier _modifier = Modifier::UNSIGNED); + explicit IntegerType(int _bits, Modifier _modifier = Modifier::Unsigned); virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual bool acceptsBinaryOperator(Token::Value _operator) const override; - virtual bool acceptsUnaryOperator(Token::Value _operator) const override; + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; virtual bool operator==(Type const& _other) const override; @@ -166,17 +184,51 @@ public: virtual MemberList const& getMembers() const { return isAddress() ? AddressMemberList : EmptyMemberList; } virtual std::string toString() const override; - virtual u256 literalValue(Literal const& _literal) const override; int getNumBits() const { return m_bits; } - bool isHash() const { return m_modifier == Modifier::HASH || m_modifier == Modifier::ADDRESS; } - bool isAddress() const { return m_modifier == Modifier::ADDRESS; } - int isSigned() const { return m_modifier == Modifier::SIGNED; } + bool isHash() const { return m_modifier == Modifier::Hash || m_modifier == Modifier::Address; } + bool isAddress() const { return m_modifier == Modifier::Address; } + bool isSigned() const { return m_modifier == Modifier::Signed; } + + static const MemberList AddressMemberList; private: int m_bits; Modifier m_modifier; - static const MemberList AddressMemberList; +}; + +/** + * Integer constants either literals or computed. Example expressions: 2, 2+10, ~10. + * There is one distinct type per value. + */ +class IntegerConstantType: public Type +{ +public: + virtual Category getCategory() const override { return Category::IntegerConstant; } + + explicit IntegerConstantType(Literal const& _literal); + explicit IntegerConstantType(bigint _value): m_value(_value) {} + + virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; + + virtual bool operator==(Type const& _other) const override; + + virtual bool canBeStored() const override { return false; } + virtual bool canLiveOutsideStorage() const override { return false; } + virtual unsigned getSizeOnStack() const override { return 1; } + + virtual std::string toString() const override; + virtual u256 literalValue(Literal const* _literal) const override; + virtual TypePointer getRealType() const override; + + /// @returns the smallest integer type that can hold the value or an empty pointer if not possible. + std::shared_ptr<IntegerType const> getIntegerType() const; + +private: + bigint m_value; }; /** @@ -185,22 +237,23 @@ private: class StaticStringType: public Type { public: - virtual Category getCategory() const override { return Category::STRING; } + virtual Category getCategory() const override { return Category::String; } /// @returns the smallest string type for the given literal or an empty pointer /// if no type fits. static std::shared_ptr<StaticStringType> smallestTypeForLiteral(std::string const& _literal); - StaticStringType(int _bytes); + explicit StaticStringType(int _bytes); virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool operator==(Type const& _other) const override; virtual unsigned getCalldataEncodedSize() const override { return m_bytes; } virtual bool isValueType() const override { return true; } virtual std::string toString() const override { return "string" + dev::toString(m_bytes); } - virtual u256 literalValue(Literal const& _literal) const override; + virtual u256 literalValue(Literal const* _literal) const override; int getNumBytes() const { return m_bytes; } @@ -215,22 +268,45 @@ class BoolType: public Type { public: BoolType() {} - virtual Category getCategory() const { return Category::BOOL; } + virtual Category getCategory() const override { return Category::Bool; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual bool acceptsBinaryOperator(Token::Value _operator) const override - { - return _operator == Token::AND || _operator == Token::OR; - } - virtual bool acceptsUnaryOperator(Token::Value _operator) const override - { - return _operator == Token::NOT || _operator == Token::DELETE; - } + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; virtual unsigned getCalldataEncodedSize() const { return 1; } virtual bool isValueType() const override { return true; } virtual std::string toString() const override { return "bool"; } - virtual u256 literalValue(Literal const& _literal) const override; + virtual u256 literalValue(Literal const* _literal) const override; +}; + +/** + * The type of a byte array, prototype for a general array. + */ +class ByteArrayType: public Type +{ +public: + enum class Location { Storage, CallData, Memory }; + + virtual Category getCategory() const override { return Category::ByteArray; } + explicit ByteArrayType(Location _location): m_location(_location) {} + virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual bool operator==(const Type& _other) const override; + virtual bool isDynamicallySized() const { return true; } + virtual unsigned getSizeOnStack() const override; + virtual std::string toString() const override { return "bytes"; } + virtual MemberList const& getMembers() const override { return s_byteArrayMemberList; } + + Location getLocation() const { return m_location; } + + /// @returns a copy of this type with location changed to @a _location + /// @todo this might move as far up as Type later + std::shared_ptr<ByteArrayType> copyForLocation(Location _location) const; + +private: + Location m_location; + static const MemberList s_byteArrayMemberList; }; /** @@ -239,27 +315,38 @@ public: class ContractType: public Type { public: - virtual Category getCategory() const override { return Category::CONTRACT; } - ContractType(ContractDefinition const& _contract): m_contract(_contract) {} - /// Contracts can be converted to themselves and to addresses. + virtual Category getCategory() const override { return Category::Contract; } + explicit ContractType(ContractDefinition const& _contract, bool _super = false): + m_contract(_contract), m_super(_super) {} + /// Contracts can be implicitly converted to super classes and to addresses. + virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; + /// 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 bool operator==(Type const& _other) const override; - virtual u256 getStorageSize() const override; virtual bool isValueType() const override { return true; } virtual std::string toString() const override; virtual MemberList const& getMembers() const override; + bool isSuper() const { return m_super; } + ContractDefinition const& getContractDefinition() const { return m_contract; } + /// Returns the function type of the constructor. Note that the location part of the function type /// is not used, as this type cannot be the type of a variable or expression. - std::shared_ptr<FunctionType const> const& getConstructorType() const; + FunctionTypePointer const& getConstructorType() const; - unsigned getFunctionIndex(std::string const& _functionName) const; + /// @returns the identifier of the function with the given name or Invalid256 if such a name does + /// not exist. + u256 getFunctionIdentifier(std::string const& _functionName) const; private: ContractDefinition const& m_contract; + /// If true, it is the "super" type of the current contract, i.e. it contains only inherited + /// members. + bool m_super; /// Type of the constructor, @see getConstructorType. Lazily initialized. - mutable std::shared_ptr<FunctionType const> m_constructorType; + mutable FunctionTypePointer m_constructorType; /// List of member types, will be lazy-initialized because of recursive references. mutable std::unique_ptr<MemberList> m_members; }; @@ -270,13 +357,9 @@ private: class StructType: public Type { public: - virtual Category getCategory() const override { return Category::STRUCT; } - StructType(StructDefinition const& _struct): m_struct(_struct) {} - virtual bool acceptsUnaryOperator(Token::Value _operator) const override - { - return _operator == Token::DELETE; - } - + virtual Category getCategory() const override { return Category::Struct; } + explicit StructType(StructDefinition const& _struct): m_struct(_struct) {} + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(Type const& _other) const override; virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override; @@ -294,6 +377,32 @@ private: }; /** + * The type of an enum instance, there is one distinct type per enum definition. + */ +class EnumType: public Type +{ +public: + virtual Category getCategory() const override { return Category::Enum; } + explicit EnumType(EnumDefinition const& _enum): m_enum(_enum) {} + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual bool operator==(Type const& _other) const override; + virtual unsigned getSizeOnStack() const override { return 1; } + virtual std::string toString() const override; + virtual bool isValueType() const override { return true; } + + virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + + EnumDefinition const& getEnumDefinition() const { return m_enum; } + /// @returns the value that the string has in the Enum + unsigned int getMemberValue(ASTString const& _member) const; + +private: + EnumDefinition const& m_enum; + /// List of member types, will be lazy-initialized because of recursive references. + mutable std::unique_ptr<MemberList> m_members; +}; + +/** * The type of a function, identified by its (return) parameter types. * @todo the return parameters should also have names, i.e. return parameters should be a struct * type. @@ -302,20 +411,47 @@ class FunctionType: public Type { public: /// The meaning of the value(s) on the stack referencing the function: - /// INTERNAL: jump tag, EXTERNAL: contract address + function index, + /// INTERNAL: jump tag, EXTERNAL: contract address + function identifier, /// BARE: contract address (non-abi contract call) /// OTHERS: special virtual function, nothing on the stack - enum class Location { INTERNAL, EXTERNAL, SEND, SHA3, SUICIDE, ECRECOVER, SHA256, RIPEMD160, BARE }; - - virtual Category getCategory() const override { return Category::FUNCTION; } + /// @todo This documentation is outdated, and Location should rather be named "Type" + enum class Location { Internal, External, Creation, Send, + SHA3, Suicide, + ECRecover, SHA256, RIPEMD160, + Log0, Log1, Log2, Log3, Log4, Event, + SetGas, SetValue, BlockHash, + Bare }; + + virtual Category getCategory() const override { return Category::Function; } explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true); - FunctionType(TypePointers const& _parameterTypes, TypePointers const& _returnParameterTypes, - Location _location = Location::INTERNAL): - m_parameterTypes(_parameterTypes), m_returnParameterTypes(_returnParameterTypes), - m_location(_location) {} + explicit FunctionType(VariableDeclaration const& _varDecl); + explicit FunctionType(EventDefinition const& _event); + FunctionType(strings const& _parameterTypes, strings const& _returnParameterTypes, + Location _location = Location::Internal, bool _arbitraryParameters = false): + FunctionType(parseElementaryTypeVector(_parameterTypes), parseElementaryTypeVector(_returnParameterTypes), + _location, _arbitraryParameters) {} + FunctionType( + TypePointers const& _parameterTypes, + TypePointers const& _returnParameterTypes, + Location _location = Location::Internal, + bool _arbitraryParameters = false, + bool _gasSet = false, + bool _valueSet = false + ): + m_parameterTypes (_parameterTypes), + m_returnParameterTypes (_returnParameterTypes), + m_location (_location), + m_arbitraryParameters (_arbitraryParameters), + m_gasSet (_gasSet), + m_valueSet (_valueSet) + {} TypePointers const& getParameterTypes() const { return m_parameterTypes; } + std::vector<std::string> const& getParameterNames() const { return m_parameterNames; } + std::vector<std::string> const getParameterTypeNames() const; TypePointers const& getReturnParameterTypes() const { return m_returnParameterTypes; } + std::vector<std::string> const& getReturnParameterNames() const { return m_returnParameterNames; } + std::vector<std::string> const getReturnParameterTypeNames() const; virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override; @@ -323,13 +459,49 @@ public: virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable function type requested.")); } virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override; + virtual MemberList const& getMembers() const override; Location const& getLocation() const { return m_location; } + /// @returns the canonical signature of this function type given the function name + /// If @a _name is not provided (empty string) then the @c m_declaration member of the + /// function type is used + std::string getCanonicalSignature(std::string const& _name = "") const; + Declaration const& getDeclaration() const + { + solAssert(m_declaration, "Requested declaration from a FunctionType that has none"); + return *m_declaration; + } + bool hasDeclaration() const { return !!m_declaration; } + bool isConstant() const { return m_isConstant; } + /// @return A shared pointer of an ASTString. + /// Can contain a nullptr in which case indicates absence of documentation + ASTPointer<ASTString> getDocumentation() const; + + /// true iff arguments are to be padded to multiples of 32 bytes for external calls + bool padArguments() const { return !(m_location == Location::SHA3 || m_location == Location::SHA256 || m_location == Location::RIPEMD160); } + bool takesArbitraryParameters() const { return m_arbitraryParameters; } + bool gasSet() const { return m_gasSet; } + bool valueSet() const { return m_valueSet; } + + /// @returns a copy of this type, where gas or value are set manually. This will never set one + /// of the parameters to fals. + TypePointer copyAndSetGasOrValue(bool _setGas, bool _setValue) const; private: + static TypePointers parseElementaryTypeVector(strings const& _types); + TypePointers m_parameterTypes; TypePointers m_returnParameterTypes; - Location m_location; + std::vector<std::string> m_parameterNames; + std::vector<std::string> m_returnParameterNames; + Location const m_location; + /// true iff the function takes an arbitrary number of arguments of arbitrary types + bool const m_arbitraryParameters = false; + bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack + bool const m_valueSet = false; ///< true iff the value to be sent is on the stack + bool m_isConstant = false; + mutable std::unique_ptr<MemberList> m_members; + Declaration const* m_declaration = nullptr; }; /** @@ -338,7 +510,7 @@ private: class MappingType: public Type { public: - virtual Category getCategory() const override { return Category::MAPPING; } + virtual Category getCategory() const override { return Category::Mapping; } MappingType(TypePointer const& _keyType, TypePointer const& _valueType): m_keyType(_keyType), m_valueType(_valueType) {} @@ -361,9 +533,10 @@ private: class VoidType: public Type { public: - virtual Category getCategory() const override { return Category::VOID; } + virtual Category getCategory() const override { return Category::Void; } VoidType() {} + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual std::string toString() const override { return "void"; } virtual bool canBeStored() const override { return false; } virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable void type requested.")); } @@ -378,19 +551,48 @@ public: class TypeType: public Type { public: - virtual Category getCategory() const override { return Category::TYPE; } - TypeType(TypePointer const& _actualType): m_actualType(_actualType) {} - + virtual Category getCategory() const override { return Category::TypeType; } + explicit TypeType(TypePointer const& _actualType, ContractDefinition const* _currentContract = nullptr): + m_actualType(_actualType), m_currentContract(_currentContract) {} TypePointer const& getActualType() const { return m_actualType; } + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } virtual bool canLiveOutsideStorage() const override { return false; } + virtual unsigned getSizeOnStack() const override { return 0; } virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; } + virtual MemberList const& getMembers() const override; private: TypePointer m_actualType; + /// Context in which this type is used (influences visibility etc.), can be nullptr. + ContractDefinition const* m_currentContract; + /// List of member types, will be lazy-initialized because of recursive references. + mutable std::unique_ptr<MemberList> m_members; +}; + + +/** + * The type of a function modifier. Not used for anything for now. + */ +class ModifierType: public Type +{ +public: + virtual Category getCategory() const override { return Category::Modifier; } + explicit ModifierType(ModifierDefinition const& _modifier); + + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } + virtual bool canBeStored() const override { return false; } + virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } + virtual bool canLiveOutsideStorage() const override { return false; } + virtual unsigned getSizeOnStack() const override { return 0; } + virtual bool operator==(Type const& _other) const override; + virtual std::string toString() const override; + +private: + TypePointers m_parameterTypes; }; @@ -401,10 +603,16 @@ private: class MagicType: public Type { public: - enum class Kind { BLOCK, MSG, TX }; - virtual Category getCategory() const override { return Category::MAGIC; } + enum class Kind { Block, Message, Transaction }; + virtual Category getCategory() const override { return Category::Magic; } + + explicit MagicType(Kind _kind); + + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override + { + return TypePointer(); + } - MagicType(Kind _kind); virtual bool operator==(Type const& _other) const; virtual bool canBeStored() const override { return false; } virtual bool canLiveOutsideStorage() const override { return true; } @@ -45,5 +45,11 @@ inline void solAssertAux(bool _condition, std::string const& _errorDescription, << ::boost::throw_line(_line)); } +inline void solAssertAux(void const* _pointer, std::string const& _errorDescription, unsigned _line, + char const* _file, char const* _function) +{ + solAssertAux(_pointer != nullptr, _errorDescription, _line, _file, _function); +} + } } diff --git a/grammar.txt b/grammar.txt index 8c34997b..a3b24687 100644 --- a/grammar.txt +++ b/grammar.txt @@ -1,12 +1,19 @@ -ContractDefinition = 'contract' Identifier '{' ContractPart* '}' -ContractPart = VariableDeclaration ';' | StructDefinition | - FunctionDefinition | 'public:' | 'private:' +ContractDefinition = 'contract' Identifier + ( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )? + '{' ContractPart* '}' +ContractPart = StateVariableDeclaration | StructDefinition | ModifierDefinition | FunctionDefinition | EnumDefinition +InheritanceSpecifier = Identifier ( '(' Expression ( ',' Expression )* ')' )? StructDefinition = 'struct' Identifier '{' ( VariableDeclaration (';' VariableDeclaration)* )? '} - -FunctionDefinition = 'function' Identifier ParameterList 'const'? +StateVariableDeclaration = TypeName ( 'public' | 'inheritable' | 'private' )? Identifier ';' +ModifierDefinition = 'modifier' Identifier ParameterList? Block +FunctionDefinition = 'function' Identifier ParameterList + ( Identifier | 'constant' | 'external' | 'public' | 'inheritable' | 'private' )* ( 'returns' ParameterList )? Block + +EnumValue = Identifier +EnumDefinition = 'enum' '{' EnumValue (',' EnumValue)* '}' ParameterList = '(' ( VariableDeclaration (',' VariableDeclaration)* )? ')' // semantic restriction: mappings and structs (recursively) containing mappings // are not allowed in argument lists @@ -33,7 +40,7 @@ Expression = Assignment | UnaryOperation | BinaryOperation | FunctionCall | NewE // The expression syntax is actually much more complicated Assignment = Expression (AssignmentOp Expression) FunctionCall = Expression '(' Expression ( ',' Expression )* ')' -NewExpression = 'new' Identifier '(' ( Expression ( ',' Expression )* ) ')' +NewExpression = 'new' Identifier MemberAccess = Expression '.' Identifier IndexAccess = Expression '[' Expresison ']' PrimaryExpression = Identifier | NumberLiteral | StringLiteral | ElementaryTypeName | '(' Expression ')' |