diff options
Diffstat (limited to 'libsolidity')
37 files changed, 458 insertions, 533 deletions
diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp index 786272e4..9e2bf6d3 100644 --- a/libsolidity/analysis/DeclarationContainer.cpp +++ b/libsolidity/analysis/DeclarationContainer.cpp @@ -96,6 +96,11 @@ void DeclarationContainer::activateVariable(ASTString const& _name) m_invisibleDeclarations.erase(_name); } +bool DeclarationContainer::isInvisible(ASTString const& _name) const +{ + return m_invisibleDeclarations.count(_name); +} + bool DeclarationContainer::registerDeclaration( Declaration const& _declaration, ASTString const* _name, diff --git a/libsolidity/analysis/DeclarationContainer.h b/libsolidity/analysis/DeclarationContainer.h index a3e0bd0a..9d7a17a3 100644 --- a/libsolidity/analysis/DeclarationContainer.h +++ b/libsolidity/analysis/DeclarationContainer.h @@ -62,6 +62,9 @@ public: /// VariableDeclarationStatements. void activateVariable(ASTString const& _name); + /// @returns true if declaration is currently invisible. + bool isInvisible(ASTString const& _name) const; + /// @returns existing declaration names similar to @a _name. /// Searches this and all parent containers. std::vector<ASTString> similarNames(ASTString const& _name) const; diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index b856544a..823378c7 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -156,7 +156,13 @@ bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration) void NameAndTypeResolver::activateVariable(string const& _name) { solAssert(m_currentScope, ""); - m_currentScope->activateVariable(_name); + // Scoped local variables are invisible before activation. + // When a local variable is activated, its name is removed + // from a scope's invisible variables. + // This is used to avoid activation of variables of same name + // in the same scope (an error is returned). + if (m_currentScope->isInvisible(_name)) + m_currentScope->activateVariable(_name); } vector<Declaration const*> NameAndTypeResolver::resolveName(ASTString const& _name, ASTNode const* _scope) const @@ -704,10 +710,6 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio dynamic_cast<EventDefinition const*>(m_currentScope) ) warnAboutShadowing = false; - // Do not warn about the constructor shadowing the contract. - if (auto fun = dynamic_cast<FunctionDefinition const*>(&_declaration)) - if (fun->isConstructor()) - warnAboutShadowing = false; // Register declaration as inactive if we are in block scope. bool inactive = diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 58b659f7..dfcbf888 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -377,19 +377,10 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable) { typeLoc = DataLocation::Storage; if (_variable.isLocalVariable()) - { - if (_variable.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050)) - typeError( - _variable.location(), - "Data location must be specified as either \"memory\" or \"storage\"." - ); - else - m_errorReporter.warning( - _variable.location(), - "Variable is declared as a storage pointer. " - "Use an explicit \"storage\" keyword to silence this warning." - ); - } + typeError( + _variable.location(), + "Data location must be specified as either \"memory\" or \"storage\"." + ); } } else diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index 323282ca..60a58665 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -51,16 +51,6 @@ void StaticAnalyzer::endVisit(ContractDefinition const&) bool StaticAnalyzer::visit(FunctionDefinition const& _function) { - const bool isInterface = m_currentContract->contractKind() == ContractDefinition::ContractKind::Interface; - - if (_function.noVisibilitySpecified()) - m_errorReporter.warning( - _function.location(), - "No visibility specified. Defaulting to \"" + - Declaration::visibilityToString(_function.visibility()) + - "\"." + - (isInterface ? " In interfaces it defaults to external." : "") - ); if (_function.isImplemented()) m_currentFunction = &_function; else diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index cd0dc2a4..5d16a33f 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -22,6 +22,7 @@ #include <libsolidity/analysis/SemVerHandler.h> #include <libsolidity/interface/ErrorReporter.h> #include <libsolidity/interface/Version.h> +#include <boost/algorithm/cxx11/all_of.hpp> using namespace std; using namespace dev; @@ -174,33 +175,19 @@ bool SyntaxChecker::visit(Break const& _breakStatement) bool SyntaxChecker::visit(Throw const& _throwStatement) { - bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050); - - if (v050) - m_errorReporter.syntaxError( - _throwStatement.location(), - "\"throw\" is deprecated in favour of \"revert()\", \"require()\" and \"assert()\"." - ); - else - m_errorReporter.warning( - _throwStatement.location(), - "\"throw\" is deprecated in favour of \"revert()\", \"require()\" and \"assert()\"." - ); + m_errorReporter.syntaxError( + _throwStatement.location(), + "\"throw\" is deprecated in favour of \"revert()\", \"require()\" and \"assert()\"." + ); return true; } bool SyntaxChecker::visit(UnaryOperation const& _operation) { - bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050); - if (_operation.getOperator() == Token::Add) - { - if (v050) - m_errorReporter.syntaxError(_operation.location(), "Use of unary + is deprecated."); - else - m_errorReporter.warning(_operation.location(), "Use of unary + is deprecated."); - } + m_errorReporter.syntaxError(_operation.location(), "Use of unary + is disallowed."); + return true; } @@ -210,28 +197,33 @@ bool SyntaxChecker::visit(PlaceholderStatement const&) return true; } -bool SyntaxChecker::visit(FunctionDefinition const& _function) +bool SyntaxChecker::visit(ContractDefinition const& _contract) { - bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050); + m_isInterface = _contract.contractKind() == ContractDefinition::ContractKind::Interface; - if (v050 && _function.noVisibilitySpecified()) - m_errorReporter.syntaxError(_function.location(), "No visibility specified."); - - if (_function.isOldStyleConstructor()) - { - if (v050) - m_errorReporter.syntaxError( - _function.location(), + ASTString const& contractName = _contract.name(); + for (FunctionDefinition const* function: _contract.definedFunctions()) + if (function->name() == contractName) + m_errorReporter.syntaxError(function->location(), "Functions are not allowed to have the same name as the contract. " "If you intend this to be a constructor, use \"constructor(...) { ... }\" to define it." ); - else - m_errorReporter.warning( - _function.location(), - "Defining constructors as functions with the same name as the contract is deprecated. " - "Use \"constructor(...) { ... }\" instead." - ); + return true; +} + +bool SyntaxChecker::visit(FunctionDefinition const& _function) +{ + bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050); + + if (_function.noVisibilitySpecified()) + { + string suggestedVisibility = _function.isFallback() || m_isInterface ? "external" : "public"; + m_errorReporter.syntaxError( + _function.location(), + "No visibility specified. Did you intend to add \"" + suggestedVisibility + "\"?" + ); } + if (!_function.isImplemented() && !_function.modifiers().empty()) { if (v050) @@ -239,11 +231,6 @@ bool SyntaxChecker::visit(FunctionDefinition const& _function) else m_errorReporter.warning(_function.location(), "Modifiers of functions without implementation are ignored." ); } - if (_function.name() == "constructor") - m_errorReporter.warning(_function.location(), - "This function is named \"constructor\" but is not the constructor of the contract. " - "If you intend this to be a constructor, use \"constructor(...) { ... }\" without the \"function\" keyword to define it." - ); return true; } @@ -260,12 +247,15 @@ bool SyntaxChecker::visit(FunctionTypeName const& _node) return true; } -bool SyntaxChecker::visit(VariableDeclaration const& _declaration) +bool SyntaxChecker::visit(VariableDeclarationStatement const& _statement) { - if (!_declaration.typeName()) - { - m_errorReporter.syntaxError(_declaration.location(), "Use of the \"var\" keyword is disallowed."); - } + // Report if none of the variable components in the tuple have a name (only possible via deprecated "var") + if (boost::algorithm::all_of_equal(_statement.declarations(), nullptr)) + m_errorReporter.syntaxError( + _statement.location(), + "The use of the \"var\" keyword is disallowed. The declaration part of the statement can be removed, since it is empty." + ); + return true; } diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index 1579df57..28a0f66e 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -66,10 +66,11 @@ private: virtual bool visit(PlaceholderStatement const& _placeholderStatement) override; + virtual bool visit(ContractDefinition const& _contract) override; virtual bool visit(FunctionDefinition const& _function) override; virtual bool visit(FunctionTypeName const& _node) override; - virtual bool visit(VariableDeclaration const& _declaration) override; + virtual bool visit(VariableDeclarationStatement const& _statement) override; virtual bool visit(StructDefinition const& _struct) override; @@ -82,6 +83,7 @@ private: bool m_versionPragmaFound = false; int m_inLoopDepth = 0; + bool m_isInterface = false; SourceUnit const* m_sourceUnit = nullptr; }; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 676b3cd6..8536e934 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -23,12 +23,14 @@ #include <libsolidity/analysis/TypeChecker.h> #include <memory> #include <boost/algorithm/string/predicate.hpp> +#include <boost/algorithm/string/join.hpp> #include <boost/range/adaptor/reversed.hpp> #include <libsolidity/ast/AST.h> #include <libsolidity/inlineasm/AsmAnalysis.h> #include <libsolidity/inlineasm/AsmAnalysisInfo.h> #include <libsolidity/inlineasm/AsmData.h> #include <libsolidity/interface/ErrorReporter.h> +#include <libdevcore/Algorithms.h> using namespace std; using namespace dev; @@ -123,10 +125,7 @@ bool TypeChecker::visit(ContractDefinition const& _contract) m_errorReporter.typeError(function->parameterList().location(), "Fallback function cannot take parameters."); if (!function->returnParameters().empty()) m_errorReporter.typeError(function->returnParameterList()->location(), "Fallback function cannot return values."); - if ( - _contract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050) && - function->visibility() != FunctionDefinition::Visibility::External - ) + if (function->visibility() != FunctionDefinition::Visibility::External) m_errorReporter.typeError(function->location(), "Fallback function must be defined as \"external\"."); } @@ -451,7 +450,7 @@ void TypeChecker::overrideError(FunctionDefinition const& function, FunctionDefi { m_errorReporter.typeError( function.location(), - SecondarySourceLocation().append("Overriden function is here:", super.location()), + SecondarySourceLocation().append("Overridden function is here:", super.location()), message ); } @@ -551,33 +550,18 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) if (arguments) { - bool v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); - if (parameterTypes.size() != arguments->size()) { - if (arguments->size() == 0 && !v050) - m_errorReporter.warning( - _inheritance.location(), - "Wrong argument count for constructor call: " + - toString(arguments->size()) + - " arguments given but expected " + - toString(parameterTypes.size()) + - "." - ); - else - { - m_errorReporter.typeError( - _inheritance.location(), - "Wrong argument count for constructor call: " + - toString(arguments->size()) + - " arguments given but expected " + - toString(parameterTypes.size()) + - "." - ); - return; - } + m_errorReporter.typeError( + _inheritance.location(), + "Wrong argument count for constructor call: " + + toString(arguments->size()) + + " arguments given but expected " + + toString(parameterTypes.size()) + + ". Remove parentheses if you do not want to provide arguments here." + ); } - for (size_t i = 0; i < arguments->size(); ++i) + for (size_t i = 0; i < std::min(arguments->size(), parameterTypes.size()); ++i) if (!type(*(*arguments)[i])->isImplicitlyConvertibleTo(*parameterTypes[i])) m_errorReporter.typeError( (*arguments)[i]->location(), @@ -610,22 +594,24 @@ bool TypeChecker::visit(StructDefinition const& _struct) m_errorReporter.typeError(member->location(), "Type cannot be used in struct."); // Check recursion, fatal error if detected. - using StructPointer = StructDefinition const*; - using StructPointersSet = set<StructPointer>; - function<void(StructPointer,StructPointersSet const&)> check = [&](StructPointer _struct, StructPointersSet const& _parents) - { - if (_parents.count(_struct)) - m_errorReporter.fatalTypeError(_struct->location(), "Recursive struct definition."); - StructPointersSet parents = _parents; - parents.insert(_struct); - for (ASTPointer<VariableDeclaration> const& member: _struct->members()) - if (type(*member)->category() == Type::Category::Struct) + auto visitor = [&](StructDefinition const& _struct, CycleDetector<StructDefinition>& _cycleDetector) + { + for (ASTPointer<VariableDeclaration> const& member: _struct.members()) + { + Type const* memberType = type(*member).get(); + while (auto arrayType = dynamic_cast<ArrayType const*>(memberType)) { - auto const& typeName = dynamic_cast<UserDefinedTypeName const&>(*member->typeName()); - check(&dynamic_cast<StructDefinition const&>(*typeName.annotation().referencedDeclaration), parents); + if (arrayType->isDynamicallySized()) + break; + memberType = arrayType->baseType().get(); } + if (auto structType = dynamic_cast<StructType const*>(memberType)) + if (_cycleDetector.run(structType->structDefinition())) + return; + } }; - check(&_struct, StructPointersSet{}); + if (CycleDetector<StructDefinition>(visitor).run(_struct) != nullptr) + m_errorReporter.fatalTypeError(_struct.location(), "Recursive struct definition."); ASTNode::listAccept(_struct.members(), *this); @@ -685,18 +671,10 @@ bool TypeChecker::visit(FunctionDefinition const& _function) { if (_function.isImplemented()) m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot have an implementation."); - if (_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050)) - { - if (_function.visibility() != FunctionDefinition::Visibility::External) - m_errorReporter.typeError(_function.location(), "Functions in interfaces must be declared external."); - } - else - { - if (_function.visibility() < FunctionDefinition::Visibility::Public) - m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot be internal or private."); - else if (_function.visibility() != FunctionDefinition::Visibility::External) - m_errorReporter.warning(_function.location(), "Functions in interfaces should be declared external."); - } + + if (_function.visibility() != FunctionDefinition::Visibility::External) + m_errorReporter.typeError(_function.location(), "Functions in interfaces must be declared external."); + if (_function.isConstructor()) m_errorReporter.typeError(_function.location(), "Constructor cannot be defined in interfaces."); } @@ -1054,9 +1032,49 @@ void TypeChecker::endVisit(EmitStatement const& _emit) m_insideEmitStatement = false; } +namespace +{ +/** + * @returns a suggested left-hand-side of a multi-variable declaration contairing + * the variable declarations given in @a _decls. + */ +string createTupleDecl(vector<ASTPointer<VariableDeclaration>> const& _decls) +{ + vector<string> components; + for (ASTPointer<VariableDeclaration> const& decl: _decls) + if (decl) + components.emplace_back(decl->annotation().type->toString(false) + " " + decl->name()); + else + components.emplace_back(); + + if (_decls.size() == 1) + return components.front(); + else + return "(" + boost::algorithm::join(components, ", ") + ")"; +} + +bool typeCanBeExpressed(vector<ASTPointer<VariableDeclaration>> const& decls) +{ + for (ASTPointer<VariableDeclaration> const& decl: decls) + { + // skip empty tuples (they can be expressed of course) + if (!decl) + continue; + + if (auto functionType = dynamic_cast<FunctionType const*>(decl->annotation().type.get())) + if ( + functionType->kind() != FunctionType::Kind::Internal && + functionType->kind() != FunctionType::Kind::External + ) + return false; + } + + return true; +} +} + bool TypeChecker::visit(VariableDeclarationStatement const& _statement) { - bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); if (!_statement.initialValue()) { // No initial value is only permitted for single variables with specified type. @@ -1073,10 +1091,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) if (varDecl.referenceLocation() == VariableDeclaration::Location::Default) errorText += " Did you mean '<type> memory " + varDecl.name() + "'?"; solAssert(m_scope, ""); - if (v050) - m_errorReporter.declarationError(varDecl.location(), errorText); - else - m_errorReporter.warning(varDecl.location(), errorText); + m_errorReporter.declarationError(varDecl.location(), errorText); } } else if (dynamic_cast<MappingType const*>(type(varDecl).get())) @@ -1098,85 +1113,34 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) else valueTypes = TypePointers{type(*_statement.initialValue())}; - // Determine which component is assigned to which variable. - // If numbers do not match, fill up if variables begin or end empty (not both). - vector<VariableDeclaration const*>& assignments = _statement.annotation().assignments; - assignments.resize(valueTypes.size(), nullptr); vector<ASTPointer<VariableDeclaration>> const& variables = _statement.declarations(); if (variables.empty()) - { - if (!valueTypes.empty()) - m_errorReporter.fatalTypeError( - _statement.location(), - "Too many components (" + - toString(valueTypes.size()) + - ") in value for variable assignment (0) needed" - ); - } + // We already have an error for this in the SyntaxChecker. + solAssert(m_errorReporter.hasErrors(), ""); else if (valueTypes.size() != variables.size()) - { - if (v050) - m_errorReporter.fatalTypeError( - _statement.location(), - "Different number of components on the left hand side (" + - toString(variables.size()) + - ") than on the right hand side (" + - toString(valueTypes.size()) + - ")." - ); - else if (!variables.front() && !variables.back()) - m_errorReporter.fatalTypeError( - _statement.location(), - "Wildcard both at beginning and end of variable declaration list is only allowed " - "if the number of components is equal." - ); - else - m_errorReporter.warning( - _statement.location(), - "Different number of components on the left hand side (" + - toString(variables.size()) + - ") than on the right hand side (" + - toString(valueTypes.size()) + - ")." - ); - } - size_t minNumValues = variables.size(); - if (!variables.empty() && (!variables.back() || !variables.front())) - --minNumValues; - if (valueTypes.size() < minNumValues) - m_errorReporter.fatalTypeError( - _statement.location(), - "Not enough components (" + - toString(valueTypes.size()) + - ") in value to assign all variables (" + - toString(minNumValues) + ")." - ); - if (valueTypes.size() > variables.size() && variables.front() && variables.back()) - m_errorReporter.fatalTypeError( + m_errorReporter.typeError( _statement.location(), - "Too many components (" + + "Different number of components on the left hand side (" + + toString(variables.size()) + + ") than on the right hand side (" + toString(valueTypes.size()) + - ") in value for variable assignment (" + - toString(minNumValues) + - " needed)." + ")." ); - bool fillRight = !variables.empty() && (!variables.back() || variables.front()); - for (size_t i = 0; i < min(variables.size(), valueTypes.size()); ++i) - if (fillRight) - assignments[i] = variables[i].get(); - else - assignments[assignments.size() - i - 1] = variables[variables.size() - i - 1].get(); - for (size_t i = 0; i < assignments.size(); ++i) + bool autoTypeDeductionNeeded = false; + + for (size_t i = 0; i < min(variables.size(), valueTypes.size()); ++i) { - if (!assignments[i]) + if (!variables[i]) continue; - VariableDeclaration const& var = *assignments[i]; + VariableDeclaration const& var = *variables[i]; solAssert(!var.value(), "Value has to be tied to statement."); TypePointer const& valueComponentType = valueTypes[i]; solAssert(!!valueComponentType, ""); if (!var.annotation().type) { + autoTypeDeductionNeeded = true; + // Infer type from value. solAssert(!var.typeName(), ""); var.annotation().type = valueComponentType->mobileType(); @@ -1220,14 +1184,6 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) } else solAssert(dynamic_cast<FixedPointType const*>(var.annotation().type.get()), "Unknown type."); - - m_errorReporter.warning( - _statement.location(), - "The type of this variable was inferred as " + - typeName + - extension + - ". This is probably not desired. Use an explicit type to silence this warning." - ); } var.accept(*this); @@ -1264,6 +1220,23 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) } } } + + if (autoTypeDeductionNeeded) + { + if (!typeCanBeExpressed(variables)) + m_errorReporter.syntaxError( + _statement.location(), + "Use of the \"var\" keyword is disallowed. " + "Type cannot be expressed in syntax." + ); + else + m_errorReporter.syntaxError( + _statement.location(), + "Use of the \"var\" keyword is disallowed. " + "Use explicit declaration `" + createTupleDecl(variables) + " = ...ยด instead." + ); + } + return false; } @@ -1337,7 +1310,6 @@ bool TypeChecker::visit(Conditional const& _conditional) bool TypeChecker::visit(Assignment const& _assignment) { - bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); requireLValue(_assignment.leftHandSide()); TypePointer t = type(_assignment.leftHandSide()); _assignment.annotation().type = t; @@ -1354,25 +1326,8 @@ bool TypeChecker::visit(Assignment const& _assignment) expectType(_assignment.rightHandSide(), *tupleType); // expectType does not cause fatal errors, so we have to check again here. - if (TupleType const* rhsType = dynamic_cast<TupleType const*>(type(_assignment.rightHandSide()).get())) - { + if (dynamic_cast<TupleType const*>(type(_assignment.rightHandSide()).get())) checkDoubleStorageAssignment(_assignment); - // @todo For 0.5.0, this code shoud move to TupleType::isImplicitlyConvertibleTo, - // but we cannot do it right now. - if (rhsType->components().size() != tupleType->components().size()) - { - string message = - "Different number of components on the left hand side (" + - toString(tupleType->components().size()) + - ") than on the right hand side (" + - toString(rhsType->components().size()) + - ")."; - if (v050) - m_errorReporter.typeError(_assignment.location(), message); - else - m_errorReporter.warning(_assignment.location(), message); - } - } } else if (t->category() == Type::Category::Mapping) { @@ -1429,14 +1384,12 @@ bool TypeChecker::visit(TupleExpression const& _tuple) } else { - bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); bool isPure = true; TypePointer inlineArrayType; for (size_t i = 0; i < components.size(); ++i) { - // Outside of an lvalue-context, the only situation where a component can be empty is (x,). - if (!components[i] && !(i == 1 && components.size() == 2)) + if (!components[i]) m_errorReporter.fatalTypeError(_tuple.location(), "Tuple component cannot be empty."); else if (components[i]) { @@ -1448,10 +1401,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple) { if (_tuple.isInlineArray()) m_errorReporter.fatalTypeError(components[i]->location(), "Array component cannot be empty."); - if (v050) - m_errorReporter.fatalTypeError(components[i]->location(), "Tuple component cannot be empty."); - else - m_errorReporter.warning(components[i]->location(), "Tuple component cannot be empty."); + m_errorReporter.typeError(components[i]->location(), "Tuple component cannot be empty."); } // Note: code generation will visit each of the expression even if they are not assigned from. @@ -1489,11 +1439,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple) if (components.size() == 1) _tuple.annotation().type = type(*components[0]); else - { - if (components.size() == 2 && !components[1]) - types.pop_back(); _tuple.annotation().type = make_shared<TupleType>(types); - } } } @@ -1684,8 +1630,6 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) else _functionCall.annotation().type = make_shared<TupleType>(returnTypes); - bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); - if (auto functionName = dynamic_cast<Identifier const*>(&_functionCall.expression())) { if (functionName->name() == "sha3" && functionType->kind() == FunctionType::Kind::SHA3) @@ -1705,23 +1649,15 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) auto const& argType = type(*arguments[i]); if (auto literal = dynamic_cast<RationalNumberType const*>(argType.get())) { - /* If no mobile type is available an error will be raised elsewhere. */ if (literal->mobileType()) + m_errorReporter.typeError( + arguments[i]->location(), + "Cannot perform packed encoding for a literal. Please convert it to an explicit type first." + ); + else { - if (v050) - m_errorReporter.typeError( - arguments[i]->location(), - "Cannot perform packed encoding for a literal. Please convert it to an explicit type first." - ); - else - m_errorReporter.warning( - arguments[i]->location(), - "The type of \"" + - argType->toString() + - "\" was inferred as " + - literal->mobileType()->toString() + - ". This is probably not desired. Use an explicit type to silence this warning." - ); + /* If no mobile type is available an error will be raised elsewhere. */ + solAssert(m_errorReporter.hasErrors(), ""); } } } @@ -1848,7 +1784,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) if (functionType->takesArbitraryParameters()) m_errorReporter.typeError( _functionCall.location(), - "Named arguments cannnot be used for functions that take arbitrary parameters." + "Named arguments cannot be used for functions that take arbitrary parameters." ); else if (parameterNames.size() > argumentNames.size()) m_errorReporter.typeError(_functionCall.location(), "Some argument names are missing."); @@ -1994,6 +1930,9 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) else ++it; } + + auto& annotation = _memberAccess.annotation(); + if (possibleMembers.size() == 0) { if (initialMemberCount == 0) @@ -2011,11 +1950,21 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) " outside of storage." ); } + string errorMsg = "Member \"" + memberName + "\" not found or not visible " + "after argument-dependent lookup in " + exprType->toString() + + (memberName == "value" ? " - did you forget the \"payable\" modifier?" : "."); + if (exprType->category() == Type::Category::Contract) + for (auto const& addressMember: IntegerType(160, IntegerType::Modifier::Address).nativeMembers(nullptr)) + if (addressMember.name == memberName) + { + Identifier const* var = dynamic_cast<Identifier const*>(&_memberAccess.expression()); + string varName = var ? var->name() : "..."; + errorMsg += " Use \"address(" + varName + ")." + memberName + "\" to access this address member."; + break; + } m_errorReporter.fatalTypeError( _memberAccess.location(), - "Member \"" + memberName + "\" not found or not visible " - "after argument-dependent lookup in " + exprType->toString() + - (memberName == "value" ? " - did you forget the \"payable\" modifier?" : "") + errorMsg ); } else if (possibleMembers.size() > 1) @@ -2023,10 +1972,9 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) _memberAccess.location(), "Member \"" + memberName + "\" not unique " "after argument-dependent lookup in " + exprType->toString() + - (memberName == "value" ? " - did you forget the \"payable\" modifier?" : "") + (memberName == "value" ? " - did you forget the \"payable\" modifier?" : ".") ); - auto& annotation = _memberAccess.annotation(); annotation.referencedDeclaration = possibleMembers.front().declaration; annotation.type = possibleMembers.front().type; @@ -2035,7 +1983,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) m_errorReporter.typeError( _memberAccess.location(), "Function \"" + memberName + "\" cannot be called on an object of type " + - exprType->toString() + " (expected " + funType->selfType()->toString() + ")" + exprType->toString() + " (expected " + funType->selfType()->toString() + ")." ); if (exprType->category() == Type::Category::Struct) @@ -2059,20 +2007,6 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) if (exprType->category() == Type::Category::Contract) { - // Warn about using address members on contracts - bool v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); - for (auto const& addressMember: IntegerType(160, IntegerType::Modifier::Address).nativeMembers(nullptr)) - if (addressMember.name == memberName && *annotation.type == *addressMember.type) - { - solAssert(!v050, "Address member still present on contract in v0.5.0."); - m_errorReporter.warning( - _memberAccess.location(), - "Using contract member \"" + memberName +"\" inherited from the address type is deprecated." + - " Convert the contract to \"address\" type to access the member," - " for example use \"address(contract)." + memberName + "\" instead." - ); - } - // Warn about using send or transfer with a non-payable fallback function. if (auto callType = dynamic_cast<FunctionType const*>(type(_memberAccess).get())) { @@ -2267,11 +2201,9 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) void TypeChecker::endVisit(Literal const& _literal) { - bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); - if (_literal.looksLikeAddress()) { - // Assign type here if it even looks like an address. This prevents double error in 050 mode for invalid address + // Assign type here if it even looks like an address. This prevents double errors for invalid addresses _literal.annotation().type = make_shared<IntegerType>(160, IntegerType::Modifier::Address); string msg; @@ -2298,20 +2230,11 @@ void TypeChecker::endVisit(Literal const& _literal) } if (_literal.isHexNumber() && _literal.subDenomination() != Literal::SubDenomination::None) - { - if (v050) - m_errorReporter.fatalTypeError( - _literal.location(), - "Hexadecimal numbers cannot be used with unit denominations. " - "You can use an expression of the form \"0x1234 * 1 day\" instead." - ); - else - m_errorReporter.warning( - _literal.location(), - "Hexadecimal numbers with unit denominations are deprecated. " - "You can use an expression of the form \"0x1234 * 1 day\" instead." - ); - } + m_errorReporter.fatalTypeError( + _literal.location(), + "Hexadecimal numbers cannot be used with unit denominations. " + "You can use an expression of the form \"0x1234 * 1 day\" instead." + ); if (_literal.subDenomination() == Literal::SubDenomination::Year) m_errorReporter.typeError( diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 2245abd6..8dc6b376 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -68,7 +68,7 @@ private: void checkContractDuplicateFunctions(ContractDefinition const& _contract); void checkContractDuplicateEvents(ContractDefinition const& _contract); void checkContractIllegalOverrides(ContractDefinition const& _contract); - /// Reports a type error with an appropiate message if overriden function signature differs. + /// Reports a type error with an appropriate message if overridden function signature differs. /// Also stores the direct super function in the AST annotations. void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super); void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message); diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 18c642c3..d936ada0 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -287,9 +287,8 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) ASTString const& member = _memberAccess.memberName(); switch (_memberAccess.expression().annotation().type->category()) { - case Type::Category::Contract: case Type::Category::Integer: - if (member == "balance" && !_memberAccess.annotation().referencedDeclaration) + if (member == "balance") mutability = StateMutability::View; break; case Type::Category::Magic: diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 16c9b2d2..7719d080 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -535,13 +535,6 @@ ReturnAnnotation& Return::annotation() const return dynamic_cast<ReturnAnnotation&>(*m_annotation); } -VariableDeclarationStatementAnnotation& VariableDeclarationStatement::annotation() const -{ - if (!m_annotation) - m_annotation = new VariableDeclarationStatementAnnotation(); - return dynamic_cast<VariableDeclarationStatementAnnotation&>(*m_annotation); -} - ExpressionAnnotation& Expression::annotation() const { if (!m_annotation) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index d703ae53..9ed3b9aa 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -614,7 +614,6 @@ public: StateMutability stateMutability() const { return m_stateMutability; } bool isConstructor() const { return m_isConstructor; } - bool isOldStyleConstructor() const { return m_isConstructor && !name().empty(); } bool isFallback() const { return !m_isConstructor && name().empty(); } bool isPayable() const { return m_stateMutability == StateMutability::Payable; } std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; } @@ -1169,11 +1168,11 @@ public: Statement const& body() const { return *m_body; } private: - /// For statement's initialization expresion. for(XXX; ; ). Can be empty + /// For statement's initialization expression. for(XXX; ; ). Can be empty ASTPointer<Statement> m_initExpression; - /// For statement's condition expresion. for(; XXX ; ). Can be empty + /// For statement's condition expression. for(; XXX ; ). Can be empty ASTPointer<Expression> m_condExpression; - /// For statement's loop expresion. for(;;XXX). Can be empty + /// For statement's loop expression. for(;;XXX). Can be empty ASTPointer<ExpressionStatement> m_loopExpression; /// The body of the loop ASTPointer<Statement> m_body; @@ -1250,13 +1249,12 @@ private: }; /** - * Definition of a variable as a statement inside a function. It requires a type name (which can - * also be "var") but the actual assignment can be missing. - * Examples: var a = 2; uint256 a; - * As a second form, multiple variables can be declared, cannot have a type and must be assigned - * right away. If the first or last component is unnamed, it can "consume" an arbitrary number - * of components. - * Examples: var (a, b) = f(); var (a,,,c) = g(); var (a,) = d(); + * Definition of one or more variables as a statement inside a function. + * If multiple variables are declared, a value has to be assigned directly. + * If only a single variable is declared, the value can be missing. + * Examples: + * uint[] memory a; uint a = 2; + * (uint a, bytes32 b, ) = f(); (, uint a, , StructName storage x) = g(); */ class VariableDeclarationStatement: public Statement { @@ -1271,13 +1269,14 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; - VariableDeclarationStatementAnnotation& annotation() const override; - std::vector<ASTPointer<VariableDeclaration>> const& declarations() const { return m_variables; } Expression const* initialValue() const { return m_initialValue.get(); } private: /// List of variables, some of which can be empty pointers (unnamed components). + /// Note that the ``m_value`` member of these is unused. Instead, ``m_initialValue`` + /// below is used, because the initial value can be a single expression assigned + /// to all variables. std::vector<ASTPointer<VariableDeclaration>> m_variables; /// The assigned expression / initial value. ASTPointer<Expression> m_initialValue; diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 5cbe42bd..e0b3f492 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -164,13 +164,6 @@ struct UserDefinedTypeNameAnnotation: TypeNameAnnotation ContractDefinition const* contractScope = nullptr; }; -struct VariableDeclarationStatementAnnotation: StatementAnnotation -{ - /// Information about which component of the value is assigned to which variable. - /// The pointer can be null to signify that the component is discarded. - std::vector<VariableDeclaration const*> assignments; -}; - struct ExpressionAnnotation: ASTAnnotation { /// Inferred type of the expression. diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index b7855668..a26828a6 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -555,8 +555,8 @@ bool ASTJsonConverter::visit(EmitStatement const& _node) bool ASTJsonConverter::visit(VariableDeclarationStatement const& _node) { Json::Value varDecs(Json::arrayValue); - for (auto const& v: _node.annotation().assignments) - appendMove(varDecs, idOrNull(v)); + for (auto const& v: _node.declarations()) + appendMove(varDecs, idOrNull(v.get())); setJsonNode(_node, "VariableDeclarationStatement", { make_pair("assignments", std::move(varDecs)), make_pair("declarations", toJson(_node.declarations())), diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 1c4eb76e..2c2d3b68 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -771,20 +771,23 @@ tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal } else if (expPoint != _literal.value().end()) { - // Parse base and exponent. Checks numeric limit. - bigint exp = bigint(string(expPoint + 1, _literal.value().end())); + // Parse mantissa and exponent. Checks numeric limit. + tuple<bool, rational> mantissa = parseRational(string(_literal.value().begin(), expPoint)); - if (exp > numeric_limits<int32_t>::max() || exp < numeric_limits<int32_t>::min()) + if (!get<0>(mantissa)) return make_tuple(false, rational(0)); + value = get<1>(mantissa); - uint32_t expAbs = bigint(abs(exp)).convert_to<uint32_t>(); - + // 0E... is always zero. + if (value == 0) + return make_tuple(true, rational(0)); - tuple<bool, rational> base = parseRational(string(_literal.value().begin(), expPoint)); + bigint exp = bigint(string(expPoint + 1, _literal.value().end())); - if (!get<0>(base)) + if (exp > numeric_limits<int32_t>::max() || exp < numeric_limits<int32_t>::min()) return make_tuple(false, rational(0)); - value = get<1>(base); + + uint32_t expAbs = bigint(abs(exp)).convert_to<uint32_t>(); if (exp < 0) { @@ -949,7 +952,7 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ RationalNumberType const& other = dynamic_cast<RationalNumberType const&>(*_other); if (Token::isCompareOp(_operator)) { - // Since we do not have a "BoolConstantType", we have to do the acutal comparison + // Since we do not have a "BoolConstantType", we have to do the actual comparison // at runtime and convert to mobile typse first. Such a comparison is not a very common // use-case and will be optimized away. TypePointer thisMobile = mobileType(); @@ -1870,47 +1873,9 @@ MemberList::MemberMap ContractType::nativeMembers(ContractDefinition const* _con &it.second->declaration() )); } - // In 0.5.0 address members are not populated into the contract. - if (!_contract->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050)) - addNonConflictingAddressMembers(members); return members; } -void ContractType::addNonConflictingAddressMembers(MemberList::MemberMap& _members) -{ - MemberList::MemberMap addressMembers = IntegerType(160, IntegerType::Modifier::Address).nativeMembers(nullptr); - for (auto const& addressMember: addressMembers) - { - bool clash = false; - for (auto const& member: _members) - { - if ( - member.name == addressMember.name && - ( - // Members with different types are not allowed - member.type->category() != addressMember.type->category() || - // Members must overload functions without clash - ( - member.type->category() == Type::Category::Function && - dynamic_cast<FunctionType const&>(*member.type).hasEqualArgumentTypes(dynamic_cast<FunctionType const&>(*addressMember.type)) - ) - ) - ) - { - clash = true; - break; - } - } - - if (!clash) - _members.push_back(MemberList::Member( - addressMember.name, - addressMember.type, - addressMember.declaration - )); - } -} - shared_ptr<FunctionType const> const& ContractType::newExpressionType() const { if (!m_constructorType) @@ -2238,25 +2203,13 @@ bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const TypePointers const& targets = tupleType->components(); if (targets.empty()) return components().empty(); - if (components().size() != targets.size() && !targets.front() && !targets.back()) - return false; // (,a,) = (1,2,3,4) - unable to position `a` in the tuple. - size_t minNumValues = targets.size(); - if (!targets.back() || !targets.front()) - --minNumValues; // wildcards can also match 0 components - if (components().size() < minNumValues) + if (components().size() != targets.size()) return false; - if (components().size() > targets.size() && targets.front() && targets.back()) - return false; // larger source and no wildcard - bool fillRight = !targets.back() || targets.front(); - for (size_t i = 0; i < min(targets.size(), components().size()); ++i) - { - auto const& s = components()[fillRight ? i : components().size() - i - 1]; - auto const& t = targets[fillRight ? i : targets.size() - i - 1]; - if (!s && t) + for (size_t i = 0; i < targets.size(); ++i) + if (!components()[i] && targets[i]) return false; - else if (s && t && !s->isImplicitlyConvertibleTo(*t)) + else if (components()[i] && targets[i] && !components()[i]->isImplicitlyConvertibleTo(*targets[i])) return false; - } return true; } else diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 4415fb4b..1a676b42 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -740,8 +740,6 @@ public: std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const; private: - static void addNonConflictingAddressMembers(MemberList::MemberMap& _members); - ContractDefinition const& m_contract; /// If true, it is the "super" type of the current contract, i.e. it contains only inherited /// members. @@ -1031,7 +1029,7 @@ public: /// @param _selfType if the function is bound, this has to be supplied and is the type of the /// expression the function is called on. bool canTakeArguments(TypePointers const& _arguments, TypePointer const& _selfType = TypePointer()) const; - /// @returns true if the types of parameters are equal (does't check return parameter types) + /// @returns true if the types of parameters are equal (doesn't check return parameter types) bool hasEqualArgumentTypes(FunctionType const& _other) const; /// @returns true if the ABI is used for this call (only meaningful for external calls) diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 4818e111..b3f1bc7e 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -1188,7 +1188,8 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) solAssert(_type.dataStoredIn(DataLocation::CallData), ""); if (!_type.isDynamicallySized()) solAssert(_type.length() < u256("0xffffffffffffffff"), ""); - solAssert(!_type.baseType()->isDynamicallyEncoded(), ""); + if (_type.baseType()->isDynamicallyEncoded()) + solUnimplemented("Calldata arrays with non-value base types are not yet supported by Solidity."); solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), ""); string functionName = diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index 6bfb3f15..3caaa1d9 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -203,7 +203,7 @@ private: std::string arrayLengthFunction(ArrayType const& _type); /// @returns the name of a function that computes the number of bytes required /// to store an array in memory given its length (internally encoded, not ABI encoded). - /// The function reverts for too large lengthes. + /// The function reverts for too large lengths. std::string arrayAllocationSizeFunction(ArrayType const& _type); /// @returns the name of a function that converts a storage slot number /// or a memory pointer to the slot number / memory pointer for the data position of an array diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index a35eea73..3b1b4ec0 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -127,10 +127,14 @@ void CompilerContext::addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent) { solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, ""); + unsigned sizeOnStack = _declaration.annotation().type->sizeOnStack(); + // Variables should not have stack size other than [1, 2], + // but that might change when new types are introduced. + solAssert(sizeOnStack == 1 || sizeOnStack == 2, ""); m_localVariables[&_declaration].push_back(unsigned(m_asm->deposit()) - _offsetToCurrent); } -void CompilerContext::removeVariable(VariableDeclaration const& _declaration) +void CompilerContext::removeVariable(Declaration const& _declaration) { solAssert(m_localVariables.count(&_declaration) && !m_localVariables[&_declaration].empty(), ""); m_localVariables[&_declaration].pop_back(); @@ -138,6 +142,25 @@ void CompilerContext::removeVariable(VariableDeclaration const& _declaration) m_localVariables.erase(&_declaration); } +void CompilerContext::removeVariablesAboveStackHeight(unsigned _stackHeight) +{ + vector<Declaration const*> toRemove; + for (auto _var: m_localVariables) + { + solAssert(!_var.second.empty(), ""); + solAssert(_var.second.back() <= stackHeight(), ""); + if (_var.second.back() >= _stackHeight) + toRemove.push_back(_var.first); + } + for (auto _var: toRemove) + removeVariable(*_var); +} + +unsigned CompilerContext::numberOfLocalVariables() const +{ + return m_localVariables.size(); +} + eth::Assembly const& CompilerContext::compiledContract(const ContractDefinition& _contract) const { auto ret = m_compiledContracts.find(&_contract); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 5776b5d1..3f357821 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -71,7 +71,11 @@ public: void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); - void removeVariable(VariableDeclaration const& _declaration); + void removeVariable(Declaration const& _declaration); + /// Removes all local variables currently allocated above _stackHeight. + void removeVariablesAboveStackHeight(unsigned _stackHeight); + /// Returns the number of currently allocated local variables. + unsigned numberOfLocalVariables() const; void setCompiledContracts(std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts) { m_compiledContracts = _contracts; } eth::Assembly const& compiledContract(ContractDefinition const& _contract) const; diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 2f45765a..2d81a106 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -363,8 +363,8 @@ void CompilerUtils::encodeToMemory( // Stack during operation: // <v1> <v2> ... <vn> <mem_start> <dyn_head_1> ... <dyn_head_r> <end_of_mem> - // The values dyn_head_i are added during the first loop and they point to the head part - // of the ith dynamic parameter, which is filled once the dynamic parts are processed. + // The values dyn_head_n are added during the first loop and they point to the head part + // of the nth dynamic parameter, which is filled once the dynamic parts are processed. // store memory start pointer m_context << Instruction::DUP1; @@ -1170,6 +1170,15 @@ void CompilerUtils::popStackSlots(size_t _amount) m_context << Instruction::POP; } +void CompilerUtils::popAndJump(unsigned _toHeight, eth::AssemblyItem const& _jumpTo) +{ + solAssert(m_context.stackHeight() >= _toHeight, ""); + unsigned amount = m_context.stackHeight() - _toHeight; + popStackSlots(amount); + m_context.appendJumpTo(_jumpTo); + m_context.adjustStackOffset(amount); +} + unsigned CompilerUtils::sizeOnStack(vector<shared_ptr<Type const>> const& _variableTypes) { unsigned size = 0; diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 0ff3ad7c..26df4765 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -241,6 +241,10 @@ public: void popStackElement(Type const& _type); /// Removes element from the top of the stack _amount times. void popStackSlots(size_t _amount); + /// Pops slots from the stack such that its height is _toHeight. + /// Adds jump to _jumpTo. + /// Readjusts the stack offset to the original value. + void popAndJump(unsigned _toHeight, eth::AssemblyItem const& _jumpTo); template <class T> static unsigned sizeOnStack(std::vector<T> const& _variables); diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 81aba21e..bbb3db3d 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -427,7 +427,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) m_context.startFunction(_function); // stack upon entry: [return address] [arg0] [arg1] ... [argn] - // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp] + // reserve additional slots: [retarg0] ... [retargm] unsigned parametersSize = CompilerUtils::sizeOnStack(_function.parameters()); if (!_function.isConstructor()) @@ -441,8 +441,6 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) for (ASTPointer<VariableDeclaration const> const& variable: _function.returnParameters()) appendStackVariableInitialisation(*variable); - for (VariableDeclaration const* localVariable: _function.localVariables()) - appendStackVariableInitialisation(*localVariable); if (_function.isConstructor()) if (auto c = m_context.nextConstructor(dynamic_cast<ContractDefinition const&>(*_function.scope()))) @@ -451,12 +449,11 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) solAssert(m_returnTags.empty(), ""); m_breakTags.clear(); m_continueTags.clear(); - m_stackCleanupForReturn = 0; m_currentFunction = &_function; m_modifierDepth = -1; + m_scopeStackHeight.clear(); appendModifierOrFunctionCode(); - solAssert(m_returnTags.empty(), ""); // Now we need to re-shuffle the stack. For this we keep a record of the stack layout @@ -467,14 +464,12 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) unsigned const c_argumentsSize = CompilerUtils::sizeOnStack(_function.parameters()); unsigned const c_returnValuesSize = CompilerUtils::sizeOnStack(_function.returnParameters()); - unsigned const c_localVariablesSize = CompilerUtils::sizeOnStack(_function.localVariables()); vector<int> stackLayout; 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>(c_localVariablesSize, -1); if (stackLayout.size() > 17) BOOST_THROW_EXCEPTION( @@ -493,18 +488,23 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) m_context << swapInstruction(stackLayout.size() - stackLayout.back() - 1); swap(stackLayout[stackLayout.back()], stackLayout.back()); } - //@todo assert that everything is in place now + for (int i = 0; i < int(stackLayout.size()); ++i) + if (stackLayout[i] != i) + solAssert(false, "Invalid stack layout on cleanup."); for (ASTPointer<VariableDeclaration const> const& variable: _function.parameters() + _function.returnParameters()) m_context.removeVariable(*variable); - for (VariableDeclaration const* localVariable: _function.localVariables()) - m_context.removeVariable(*localVariable); m_context.adjustStackOffset(-(int)c_returnValuesSize); /// The constructor and the fallback function doesn't to jump out. - if (!_function.isConstructor() && !_function.isFallback()) - m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); + if (!_function.isConstructor()) + { + solAssert(m_context.numberOfLocalVariables() == 0, ""); + if (!_function.isFallback()) + m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); + } + return false; } @@ -669,14 +669,14 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement) eth::AssemblyItem loopStart = m_context.newTag(); eth::AssemblyItem loopEnd = m_context.newTag(); - m_breakTags.push_back(loopEnd); + m_breakTags.push_back({loopEnd, m_context.stackHeight()}); m_context << loopStart; if (_whileStatement.isDoWhile()) { eth::AssemblyItem condition = m_context.newTag(); - m_continueTags.push_back(condition); + m_continueTags.push_back({condition, m_context.stackHeight()}); _whileStatement.body().accept(*this); @@ -687,7 +687,7 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement) } else { - m_continueTags.push_back(loopStart); + m_continueTags.push_back({loopStart, m_context.stackHeight()}); compileExpression(_whileStatement.condition()); m_context << Instruction::ISZERO; m_context.appendConditionalJumpTo(loopEnd); @@ -712,12 +712,14 @@ bool ContractCompiler::visit(ForStatement const& _forStatement) eth::AssemblyItem loopStart = m_context.newTag(); eth::AssemblyItem loopEnd = m_context.newTag(); eth::AssemblyItem loopNext = m_context.newTag(); - m_continueTags.push_back(loopNext); - m_breakTags.push_back(loopEnd); + + storeStackHeight(&_forStatement); if (_forStatement.initializationExpression()) _forStatement.initializationExpression()->accept(*this); + m_breakTags.push_back({loopEnd, m_context.stackHeight()}); + m_continueTags.push_back({loopNext, m_context.stackHeight()}); m_context << loopStart; // if there is no terminating condition in for, default is to always be true @@ -737,11 +739,16 @@ bool ContractCompiler::visit(ForStatement const& _forStatement) _forStatement.loopExpression()->accept(*this); m_context.appendJumpTo(loopStart); + m_context << loopEnd; m_continueTags.pop_back(); m_breakTags.pop_back(); + // For the case where no break/return is executed: + // loop initialization variables have to be freed + popScopedVariables(&_forStatement); + checker.check(); return false; } @@ -750,7 +757,7 @@ bool ContractCompiler::visit(Continue const& _continueStatement) { CompilerContext::LocationSetter locationSetter(m_context, _continueStatement); solAssert(!m_continueTags.empty(), ""); - m_context.appendJumpTo(m_continueTags.back()); + CompilerUtils(m_context).popAndJump(m_continueTags.back().second, m_continueTags.back().first); return false; } @@ -758,7 +765,7 @@ bool ContractCompiler::visit(Break const& _breakStatement) { CompilerContext::LocationSetter locationSetter(m_context, _breakStatement); solAssert(!m_breakTags.empty(), ""); - m_context.appendJumpTo(m_breakTags.back()); + CompilerUtils(m_context).popAndJump(m_breakTags.back().second, m_breakTags.back().first); return false; } @@ -784,10 +791,8 @@ bool ContractCompiler::visit(Return const& _return) for (auto const& retVariable: boost::adaptors::reverse(returnParameters)) CompilerUtils(m_context).moveToStackVariable(*retVariable); } - for (unsigned i = 0; i < m_stackCleanupForReturn; ++i) - m_context << Instruction::POP; - m_context.appendJumpTo(m_returnTags.back()); - m_context.adjustStackOffset(m_stackCleanupForReturn); + + CompilerUtils(m_context).popAndJump(m_returnTags.back().second, m_returnTags.back().first); return false; } @@ -810,8 +815,15 @@ bool ContractCompiler::visit(EmitStatement const& _emit) bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement) { - StackHeightChecker checker(m_context); CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement); + + // Local variable slots are reserved when their declaration is visited, + // and freed in the end of their scope. + for (auto _decl: _variableDeclarationStatement.declarations()) + if (_decl) + appendStackVariableInitialisation(*_decl); + + StackHeightChecker checker(m_context); if (Expression const* expression = _variableDeclarationStatement.initialValue()) { CompilerUtils utils(m_context); @@ -821,20 +833,19 @@ bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclar valueTypes = tupleType->components(); else valueTypes = TypePointers{expression->annotation().type}; - auto const& assignments = _variableDeclarationStatement.annotation().assignments; - solAssert(assignments.size() == valueTypes.size(), ""); - for (size_t i = 0; i < assignments.size(); ++i) + auto const& declarations = _variableDeclarationStatement.declarations(); + solAssert(declarations.size() == valueTypes.size(), ""); + for (size_t i = 0; i < declarations.size(); ++i) { - size_t j = assignments.size() - i - 1; + size_t j = declarations.size() - i - 1; solAssert(!!valueTypes[j], ""); - VariableDeclaration const* varDecl = assignments[j]; - if (!varDecl) - utils.popStackElement(*valueTypes[j]); - else + if (VariableDeclaration const* varDecl = declarations[j].get()) { utils.convertType(*valueTypes[j], *varDecl->annotation().type); utils.moveToStackVariable(*varDecl); } + else + utils.popStackElement(*valueTypes[j]); } } checker.check(); @@ -861,6 +872,18 @@ bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement) return true; } +bool ContractCompiler::visit(Block const& _block) +{ + storeStackHeight(&_block); + return true; +} + +void ContractCompiler::endVisit(Block const& _block) +{ + // Frees local variables declared in the scope of this block. + popScopedVariables(&_block); +} + void ContractCompiler::appendMissingFunctions() { while (Declaration const* function = m_context.nextFunctionToCompile()) @@ -916,27 +939,19 @@ void ContractCompiler::appendModifierOrFunctionCode() modifier.parameters()[i]->annotation().type ); } - for (VariableDeclaration const* localVariable: modifier.localVariables()) - { - addedVariables.push_back(localVariable); - appendStackVariableInitialisation(*localVariable); - } - stackSurplus = - CompilerUtils::sizeOnStack(modifier.parameters()) + - CompilerUtils::sizeOnStack(modifier.localVariables()); + stackSurplus = CompilerUtils::sizeOnStack(modifier.parameters()); codeBlock = &modifier.body(); } } if (codeBlock) { - m_returnTags.push_back(m_context.newTag()); - + m_returnTags.push_back({m_context.newTag(), m_context.stackHeight()}); codeBlock->accept(*this); solAssert(!m_returnTags.empty(), ""); - m_context << m_returnTags.back(); + m_context << m_returnTags.back().first; m_returnTags.pop_back(); CompilerUtils(m_context).popStackSlots(stackSurplus); @@ -983,3 +998,20 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime() const a << u256(0x20) << u256(0) << Instruction::RETURN; return make_shared<eth::Assembly>(a); } + +void ContractCompiler::popScopedVariables(ASTNode const* _node) +{ + unsigned blockHeight = m_scopeStackHeight.at(m_modifierDepth).at(_node); + m_context.removeVariablesAboveStackHeight(blockHeight); + solAssert(m_context.stackHeight() >= blockHeight, ""); + unsigned stackDiff = m_context.stackHeight() - blockHeight; + CompilerUtils(m_context).popStackSlots(stackDiff); + m_scopeStackHeight[m_modifierDepth].erase(_node); + if (m_scopeStackHeight[m_modifierDepth].size() == 0) + m_scopeStackHeight.erase(m_modifierDepth); +} + +void ContractCompiler::storeStackHeight(ASTNode const* _node) +{ + m_scopeStackHeight[m_modifierDepth][_node] = m_context.stackHeight(); +} diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 02a3452f..8516ec2c 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -109,6 +109,8 @@ private: virtual bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; virtual bool visit(ExpressionStatement const& _expressionStatement) override; virtual bool visit(PlaceholderStatement const&) override; + virtual bool visit(Block const& _block) override; + virtual void endVisit(Block const& _block) override; /// Repeatedly visits all function which are referenced but which are not compiled yet. void appendMissingFunctions(); @@ -123,19 +125,31 @@ private: /// @returns the runtime assembly for clone contracts. eth::AssemblyPointer cloneRuntime() const; + /// Frees the variables of a certain scope (to be used when leaving). + void popScopedVariables(ASTNode const* _node); + + /// Sets the stack height for the visited loop. + void storeStackHeight(ASTNode const* _node); + bool const m_optimise; /// Pointer to the runtime compiler in case this is a creation compiler. ContractCompiler* m_runtimeCompiler = nullptr; CompilerContext& m_context; - 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 - /// Tag to jump to for a "return" statement, needs to be stacked because of modifiers. - std::vector<eth::AssemblyItem> m_returnTags; + /// Tag to jump to for a "break" statement and the stack height after freeing the local loop variables. + std::vector<std::pair<eth::AssemblyItem, unsigned>> m_breakTags; + /// Tag to jump to for a "continue" statement and the stack height after freeing the local loop variables. + std::vector<std::pair<eth::AssemblyItem, unsigned>> m_continueTags; + /// Tag to jump to for a "return" statement and the stack height after freeing the local function or modifier variables. + /// Needs to be stacked because of modifiers. + std::vector<std::pair<eth::AssemblyItem, unsigned>> m_returnTags; unsigned m_modifierDepth = 0; FunctionDefinition const* m_currentFunction = nullptr; - unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag + // arguments for base constructors, filled in derived-to-base order std::map<FunctionDefinition const*, ASTNode const*> const* m_baseArguments; + + /// Stores the variables that were declared inside a specific scope, for each modifier depth. + std::map<unsigned, std::map<ASTNode const*, unsigned>> m_scopeStackHeight; }; } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 2e548e32..54518906 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -349,6 +349,10 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) case Token::Inc: // ++ (pre- or postfix) case Token::Dec: // -- (pre- or postfix) solAssert(!!m_currentLValue, "LValue not retrieved."); + solUnimplementedAssert( + _unaryOperation.annotation().type->category() != Type::Category::FixedPoint, + "Not yet implemented - FixedPointType." + ); m_currentLValue->retrieveValue(_unaryOperation.location()); if (!_unaryOperation.isPrefixOperation()) { @@ -1210,63 +1214,52 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) switch (_memberAccess.expression().annotation().type->category()) { case Type::Category::Contract: - case Type::Category::Integer: { - bool alsoSearchInteger = false; - if (_memberAccess.expression().annotation().type->category() == Type::Category::Contract) + ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.expression().annotation().type); + if (type.isSuper()) { - ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.expression().annotation().type); - if (type.isSuper()) - { - solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved."); - utils().pushCombinedFunctionEntryLabel(m_context.superFunction( - dynamic_cast<FunctionDefinition const&>(*_memberAccess.annotation().referencedDeclaration), - type.contractDefinition() - )); - } + solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved."); + utils().pushCombinedFunctionEntryLabel(m_context.superFunction( + dynamic_cast<FunctionDefinition const&>(*_memberAccess.annotation().referencedDeclaration), + type.contractDefinition() + )); + } + // ordinary contract type + else if (Declaration const* declaration = _memberAccess.annotation().referencedDeclaration) + { + u256 identifier; + if (auto const* variable = dynamic_cast<VariableDeclaration const*>(declaration)) + identifier = FunctionType(*variable).externalIdentifier(); + else if (auto const* function = dynamic_cast<FunctionDefinition const*>(declaration)) + identifier = FunctionType(*function).externalIdentifier(); else - { - // ordinary contract type - if (Declaration const* declaration = _memberAccess.annotation().referencedDeclaration) - { - u256 identifier; - if (auto const* variable = dynamic_cast<VariableDeclaration const*>(declaration)) - identifier = FunctionType(*variable).externalIdentifier(); - else if (auto const* function = dynamic_cast<FunctionDefinition const*>(declaration)) - identifier = FunctionType(*function).externalIdentifier(); - else - solAssert(false, "Contract member is neither variable nor function."); - utils().convertType(type, IntegerType(160, IntegerType::Modifier::Address), true); - m_context << identifier; - } - else - // not found in contract, search in members inherited from address - alsoSearchInteger = true; - } + solAssert(false, "Contract member is neither variable nor function."); + utils().convertType(type, IntegerType(160, IntegerType::Modifier::Address), true); + m_context << identifier; } else - alsoSearchInteger = true; - - if (alsoSearchInteger) + solAssert(false, "Invalid member access in contract"); + break; + } + case Type::Category::Integer: + { + if (member == "balance") { - if (member == "balance") - { - utils().convertType( - *_memberAccess.expression().annotation().type, - IntegerType(160, IntegerType::Modifier::Address), - true - ); - m_context << Instruction::BALANCE; - } - else if ((set<string>{"send", "transfer", "call", "callcode", "delegatecall"}).count(member)) - utils().convertType( - *_memberAccess.expression().annotation().type, - IntegerType(160, IntegerType::Modifier::Address), - true - ); - else - solAssert(false, "Invalid member access to integer"); + utils().convertType( + *_memberAccess.expression().annotation().type, + IntegerType(160, IntegerType::Modifier::Address), + true + ); + m_context << Instruction::BALANCE; } + else if ((set<string>{"send", "transfer", "call", "callcode", "delegatecall"}).count(member)) + utils().convertType( + *_memberAccess.expression().annotation().type, + IntegerType(160, IntegerType::Modifier::Address), + true + ); + else + solAssert(false, "Invalid member access to integer"); break; } case Type::Category::Function: @@ -1647,12 +1640,12 @@ 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 c_isSigned = type.isSigned(); - if (_type.category() == Type::Category::FixedPoint) solUnimplemented("Not yet implemented - FixedPointType."); + IntegerType const& type = dynamic_cast<IntegerType const&>(_type); + bool const c_isSigned = type.isSigned(); + switch (_operator) { case Token::Add: @@ -1751,7 +1744,7 @@ void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type co { if (c_valueSigned) // In the following assembly snippet, xor_mask will be zero, if value_to_shift is positive. - // Therefor xor'ing with xor_mask is the identity and the computation reduces to + // Therefore xor'ing with xor_mask is the identity and the computation reduces to // div(value_to_shift, exp(2, shift_amount)), which is correct, since for positive values // arithmetic right shift is dividing by a power of two (which, as a bitwise operation, results // in discarding bits on the right and filling with zeros from the left). @@ -1810,15 +1803,11 @@ void ExpressionCompiler::appendExternalFunctionCall( if (_functionType.bound()) utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack()); - bool const v050 = m_context.experimentalFeatureActive(ExperimentalFeature::V050); auto funKind = _functionType.kind(); bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall; bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode; bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; - bool useStaticCall = - _functionType.stateMutability() <= StateMutability::View && - v050 && - m_context.evmVersion().hasStaticCall(); + bool useStaticCall = _functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall(); bool haveReturndatacopy = m_context.evmVersion().supportsReturndata(); unsigned retSize = 0; @@ -1879,7 +1868,7 @@ void ExpressionCompiler::appendExternalFunctionCall( { m_context << u256(0); utils().fetchFreeMemoryPointer(); - // This touches too much, but that way we save some rounding arithmetics + // This touches too much, but that way we save some rounding arithmetic m_context << u256(retSize) << Instruction::ADD << Instruction::MSTORE; } } diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index a4d9500b..e2a51267 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -95,7 +95,7 @@ bool SMTChecker::visit(FunctionDefinition const& _function) void SMTChecker::endVisit(FunctionDefinition const&) { - // TOOD we could check for "reachability", i.e. satisfiability here. + // TODO we could check for "reachability", i.e. satisfiability here. // We only handle local variables, so we clear at the beginning of the function. // If we add storage variables, those should be cleared differently. removeLocalVariables(); diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 7df9ab88..cd429a98 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -150,7 +150,7 @@ assembly::Statement Parser::parseStatement() expectToken(Token::Comma); elementary = parseElementaryOperation(); if (elementary.type() != typeid(assembly::Identifier)) - fatalParserError("Variable name expected in multiple assignemnt."); + fatalParserError("Variable name expected in multiple assignment."); assignment.variableNames.emplace_back(boost::get<assembly::Identifier>(elementary)); } while (currentToken() == Token::Comma); diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp index a4b0265e..46fa1d6b 100644 --- a/libsolidity/interface/AssemblyStack.cpp +++ b/libsolidity/interface/AssemblyStack.cpp @@ -119,7 +119,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const julia::EVMAssembly assembly(true); julia::CodeTransform(assembly, *m_analysisInfo, m_language == Language::Yul, true)(*m_parserResult); object.bytecode = make_shared<eth::LinkerObject>(assembly.finalize()); - /// TOOD: fill out text representation + /// TODO: fill out text representation return object; } case Machine::eWasm: diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 4359c3fa..0578ac86 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -93,7 +93,7 @@ public: m_errorList(), m_errorReporter(m_errorList) {} - /// @returns the list of errors that occured during parsing and type checking. + /// @returns the list of errors that occurred during parsing and type checking. ErrorList const& errors() const { return m_errorReporter.errors(); } /// @returns the current state. diff --git a/libsolidity/interface/ErrorReporter.h b/libsolidity/interface/ErrorReporter.h index d1a0030f..fd53587a 100644 --- a/libsolidity/interface/ErrorReporter.h +++ b/libsolidity/interface/ErrorReporter.h @@ -92,6 +92,12 @@ public: void clear(); + /// @returns true iff there is any error (ignores warnings). + bool hasErrors() const + { + return m_errorCount > 0; + } + private: void error(Error::Type _type, SourceLocation const& _location, diff --git a/libsolidity/interface/Natspec.cpp b/libsolidity/interface/Natspec.cpp index 7f7084ef..29a5b798 100644 --- a/libsolidity/interface/Natspec.cpp +++ b/libsolidity/interface/Natspec.cpp @@ -36,6 +36,10 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef) Json::Value doc; Json::Value methods(Json::objectValue); + string notice = extractDoc(_contractDef.annotation().docTags, "notice"); + if (!notice.empty()) + doc["notice"] = Json::Value(notice); + for (auto const& it: _contractDef.interfaceFunctions()) if (it.second->hasDeclaration()) if (auto const* f = dynamic_cast<FunctionDefinition const*>(&it.second->declaration())) @@ -65,6 +69,9 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef) auto title = extractDoc(_contractDef.annotation().docTags, "title"); if (!title.empty()) doc["title"] = title; + auto dev = extractDoc(_contractDef.annotation().docTags, "dev"); + if (!dev.empty()) + doc["details"] = Json::Value(dev); for (auto const& it: _contractDef.interfaceFunctions()) { diff --git a/libsolidity/interface/Natspec.h b/libsolidity/interface/Natspec.h index 0701f821..6a827d3b 100644 --- a/libsolidity/interface/Natspec.h +++ b/libsolidity/interface/Natspec.h @@ -45,7 +45,7 @@ public: /// @param _contractDef The contract definition /// @return A JSON representation of the contract's user documentation static Json::Value userDocumentation(ContractDefinition const& _contractDef); - /// Genereates the Developer's documentation of the contract + /// Generates the Developer's documentation of the contract /// @param _contractDef The contract definition /// @return A JSON representation /// of the contract's developer documentation diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index 11a0b4c2..2772394a 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -31,7 +31,7 @@ namespace solidity { /** - * Standard JSON compiler interface, which expects a JSON input and returns a JSON ouput. + * Standard JSON compiler interface, which expects a JSON input and returns a JSON output. * See docs/using-the-compiler#compiler-input-and-output-json-description. */ class StandardCompiler: boost::noncopyable diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index e2bd6fb4..0bee2a91 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -239,13 +239,10 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _exp Token::Value currentTokenValue = m_scanner->currentToken(); if (currentTokenValue == Token::RBrace) break; - else if ( - currentTokenValue == Token::Function || - (currentTokenValue == Token::Identifier && m_scanner->currentLiteral() == "constructor") - ) + else if (currentTokenValue == Token::Function || currentTokenValue == Token::Constructor) // This can be a function or a state variable of function type (especially // complicated to distinguish fallback function from function type state variable) - subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable(name.get())); + subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable()); else if (currentTokenValue == Token::Struct) subNodes.push_back(parseStructDefinition()); else if (currentTokenValue == Token::Enum) @@ -340,30 +337,31 @@ StateMutability Parser::parseStateMutability(Token::Value _token) return stateMutability; } -Parser::FunctionHeaderParserResult Parser::parseFunctionHeader( - bool _forceEmptyName, - bool _allowModifiers, - ASTString const* _contractName -) +Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers) { RecursionGuard recursionGuard(*this); FunctionHeaderParserResult result; result.isConstructor = false; - if (m_scanner->currentToken() == Token::Identifier && m_scanner->currentLiteral() == "constructor") + if (m_scanner->currentToken() == Token::Constructor) result.isConstructor = true; else if (m_scanner->currentToken() != Token::Function) solAssert(false, "Function or constructor expected."); m_scanner->next(); - if (result.isConstructor || _forceEmptyName || m_scanner->currentToken() == Token::LParen) + if (result.isConstructor) result.name = make_shared<ASTString>(); + else if (_forceEmptyName || m_scanner->currentToken() == Token::LParen) + result.name = make_shared<ASTString>(); + else if (m_scanner->currentToken() == Token::Constructor) + fatalParserError(string( + "This function is named \"constructor\" but is not the constructor of the contract. " + "If you intend this to be a constructor, use \"constructor(...) { ... }\" without the \"function\" keyword to define it." + )); else result.name = expectIdentifierToken(); - if (!result.name->empty() && _contractName && *result.name == *_contractName) - result.isConstructor = true; VarDeclParserOptions options; options.allowLocationSpecifier = true; @@ -435,7 +433,7 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader( return result; } -ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName) +ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable() { RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); @@ -443,7 +441,7 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A if (m_scanner->currentCommentLiteral() != "") docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral()); - FunctionHeaderParserResult header = parseFunctionHeader(false, true, _contractName); + FunctionHeaderParserResult header = parseFunctionHeader(false, true); if ( header.isConstructor || diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 08653364..c906771a 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -74,12 +74,8 @@ private: ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier(); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); StateMutability parseStateMutability(Token::Value _token); - FunctionHeaderParserResult parseFunctionHeader( - bool _forceEmptyName, - bool _allowModifiers, - ASTString const* _contractName = nullptr - ); - ASTPointer<ASTNode> parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName); + FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); + ASTPointer<ASTNode> parseFunctionDefinitionOrFunctionTypeStateVariable(); ASTPointer<FunctionDefinition> parseFunctionDefinition(ASTString const* _contractName); ASTPointer<StructDefinition> parseStructDefinition(); ASTPointer<EnumDefinition> parseEnumDefinition(); diff --git a/libsolidity/parsing/Scanner.h b/libsolidity/parsing/Scanner.h index 0adaa6fd..8a3011eb 100644 --- a/libsolidity/parsing/Scanner.h +++ b/libsolidity/parsing/Scanner.h @@ -223,7 +223,7 @@ private: bool isSourcePastEndOfInput() const { return m_source.isPastEndOfInput(); } TokenDesc m_skippedComment; // desc for current skipped comment - TokenDesc m_nextSkippedComment; // desc for next skiped comment + TokenDesc m_nextSkippedComment; // desc for next skipped comment TokenDesc m_currentToken; // desc for current token (as returned by Next()) TokenDesc m_nextToken; // desc for next token (one token look-ahead) diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h index cb855cbe..7ce24e69 100644 --- a/libsolidity/parsing/Token.h +++ b/libsolidity/parsing/Token.h @@ -144,6 +144,7 @@ namespace solidity K(Assembly, "assembly", 0) \ K(Break, "break", 0) \ K(Constant, "constant", 0) \ + K(Constructor, "constructor", 0) \ K(Continue, "continue", 0) \ K(Contract, "contract", 0) \ K(Do, "do", 0) \ |