diff options
Diffstat (limited to 'libsolidity')
46 files changed, 2479 insertions, 736 deletions
diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index d8f1603a..a54b8c8d 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -39,39 +39,39 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared< make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)), make_shared<MagicVariableDeclaration>("now", make_shared<IntegerType>(256)), make_shared<MagicVariableDeclaration>("suicide", - make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Selfdestruct)), + make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)), make_shared<MagicVariableDeclaration>("selfdestruct", - make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Selfdestruct)), + make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)), make_shared<MagicVariableDeclaration>("addmod", - make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Location::AddMod)), + make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod)), make_shared<MagicVariableDeclaration>("mulmod", - make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Location::MulMod)), + make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod)), make_shared<MagicVariableDeclaration>("sha3", - make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA3, true)), + make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true)), make_shared<MagicVariableDeclaration>("keccak256", - make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA3, true)), + make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true)), make_shared<MagicVariableDeclaration>("log0", - make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Location::Log0)), + make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)), make_shared<MagicVariableDeclaration>("log1", - make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Location::Log1)), + make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log1)), make_shared<MagicVariableDeclaration>("log2", - make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log2)), + make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log2)), make_shared<MagicVariableDeclaration>("log3", - make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log3)), + make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log3)), make_shared<MagicVariableDeclaration>("log4", - make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log4)), + make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log4)), make_shared<MagicVariableDeclaration>("sha256", - make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA256, true)), + make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true)), make_shared<MagicVariableDeclaration>("ecrecover", - make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)), + make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover)), make_shared<MagicVariableDeclaration>("ripemd160", - make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true)), + make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true)), make_shared<MagicVariableDeclaration>("assert", - make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Location::Assert)), + make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert)), make_shared<MagicVariableDeclaration>("require", - make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Location::Require)), + make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Require)), make_shared<MagicVariableDeclaration>("revert", - make_shared<FunctionType>(strings(), strings(), FunctionType::Location::Revert))}) + make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert))}) { } diff --git a/libsolidity/analysis/PostTypeChecker.cpp b/libsolidity/analysis/PostTypeChecker.cpp index cae77c74..e8da3ca4 100644 --- a/libsolidity/analysis/PostTypeChecker.cpp +++ b/libsolidity/analysis/PostTypeChecker.cpp @@ -58,6 +58,9 @@ void PostTypeChecker::endVisit(ContractDefinition const&) for (auto declaration: m_constVariables) if (auto identifier = findCycle(declaration)) typeError(declaration->location(), "The value of the constant " + declaration->name() + " has a cyclic dependency via " + identifier->name() + "."); + + m_constVariables.clear(); + m_constVariableDependencies.clear(); } bool PostTypeChecker::visit(VariableDeclaration const& _variable) diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 37bcb2d9..9433976a 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -25,9 +25,12 @@ #include <libsolidity/analysis/NameAndTypeResolver.h> #include <libsolidity/interface/Exceptions.h> #include <libsolidity/analysis/ConstantEvaluator.h> -#include <libsolidity/inlineasm/AsmCodeGen.h> +#include <libsolidity/inlineasm/AsmAnalysis.h> +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> #include <libsolidity/inlineasm/AsmData.h> +#include <boost/algorithm/string.hpp> + using namespace std; using namespace dev; using namespace dev::solidity; @@ -158,21 +161,40 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) { - // We need to perform a full code generation pass here as inline assembly does not distinguish - // reference resolution and code generation. // Errors created in this stage are completely ignored because we do not yet know // the type and size of external identifiers, which would result in false errors. + // The only purpose of this step is to fill the inline assembly annotation with + // external references. ErrorList errorsIgnored; - assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errorsIgnored); - codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly&, assembly::CodeGenerator::IdentifierContext) { + assembly::ExternalIdentifierAccess::Resolver resolver = + [&](assembly::Identifier const& _identifier, assembly::IdentifierContext) { auto declarations = m_resolver.nameFromCurrentScope(_identifier.name); + bool isSlot = boost::algorithm::ends_with(_identifier.name, "_slot"); + bool isOffset = boost::algorithm::ends_with(_identifier.name, "_offset"); + if (isSlot || isOffset) + { + // special mode to access storage variables + if (!declarations.empty()) + // the special identifier exists itself, we should not allow that. + return size_t(-1); + string realName = _identifier.name.substr(0, _identifier.name.size() - ( + isSlot ? + string("_slot").size() : + string("_offset").size() + )); + declarations = m_resolver.nameFromCurrentScope(realName); + } if (declarations.size() != 1) - return false; - _inlineAssembly.annotation().externalReferences[&_identifier] = declarations.front(); - // At this stage we neither know the code to generate nor the stack size of the identifier, - // so we do not modify assembly. - return true; - }); + return size_t(-1); + _inlineAssembly.annotation().externalReferences[&_identifier].isSlot = isSlot; + _inlineAssembly.annotation().externalReferences[&_identifier].isOffset = isOffset; + _inlineAssembly.annotation().externalReferences[&_identifier].declaration = declarations.front(); + return size_t(1); + }; + + // Will be re-generated later with correct information + assembly::AsmAnalysisInfo analysisInfo; + assembly::AsmAnalyzer(analysisInfo, errorsIgnored, resolver).analyze(_inlineAssembly.operations()); return false; } diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index c39f874e..369376fa 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -48,13 +48,65 @@ void StaticAnalyzer::endVisit(ContractDefinition const&) bool StaticAnalyzer::visit(FunctionDefinition const& _function) { + if (_function.isImplemented()) + m_currentFunction = &_function; + else + solAssert(!m_currentFunction, ""); + solAssert(m_localVarUseCount.empty(), ""); m_nonPayablePublic = _function.isPublic() && !_function.isPayable(); return true; } void StaticAnalyzer::endVisit(FunctionDefinition const&) { + m_currentFunction = nullptr; m_nonPayablePublic = false; + for (auto const& var: m_localVarUseCount) + if (var.second == 0) + warning(var.first->location(), "Unused local variable"); + m_localVarUseCount.clear(); +} + +bool StaticAnalyzer::visit(Identifier const& _identifier) +{ + if (m_currentFunction) + if (auto var = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration)) + { + solAssert(!var->name().empty(), ""); + if (var->isLocalVariable()) + m_localVarUseCount[var] += 1; + } + return true; +} + +bool StaticAnalyzer::visit(VariableDeclaration const& _variable) +{ + if (m_currentFunction) + { + solAssert(_variable.isLocalVariable(), ""); + if (_variable.name() != "") + // This is not a no-op, the entry might pre-exist. + m_localVarUseCount[&_variable] += 0; + } + return true; +} + +bool StaticAnalyzer::visit(Return const& _return) +{ + // If the return has an expression, it counts as + // a "use" of the return parameters. + if (m_currentFunction && _return.expression()) + for (auto const& var: m_currentFunction->returnParameters()) + if (!var->name().empty()) + m_localVarUseCount[var.get()] += 1; + return true; +} + +bool StaticAnalyzer::visit(ExpressionStatement const& _statement) +{ + if (_statement.expression().annotation().isPure) + warning(_statement.location(), "Statement has no effect."); + return true; } bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h index 0cb961bd..ab72e7d9 100644 --- a/libsolidity/analysis/StaticAnalyzer.h +++ b/libsolidity/analysis/StaticAnalyzer.h @@ -60,6 +60,10 @@ private: virtual bool visit(FunctionDefinition const& _function) override; virtual void endVisit(FunctionDefinition const& _function) override; + virtual bool visit(ExpressionStatement const& _statement) override; + virtual bool visit(VariableDeclaration const& _variable) override; + virtual bool visit(Identifier const& _identifier) override; + virtual bool visit(Return const& _return) override; virtual bool visit(MemberAccess const& _memberAccess) override; ErrorList& m_errors; @@ -69,6 +73,11 @@ private: /// Flag that indicates whether a public function does not contain the "payable" modifier. bool m_nonPayablePublic = false; + + /// Number of uses of each (named) local variable in a function, counter is initialized with zero. + std::map<VariableDeclaration const*, int> m_localVarUseCount; + + FunctionDefinition const* m_currentFunction = nullptr; }; } diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 89014133..94e82a87 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -32,6 +32,16 @@ bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot) return Error::containsOnlyWarnings(m_errors); } +void SyntaxChecker::warning(SourceLocation const& _location, string const& _description) +{ + auto err = make_shared<Error>(Error::Type::Warning); + *err << + errinfo_sourceLocation(_location) << + errinfo_comment(_description); + + m_errors.push_back(err); +} + void SyntaxChecker::syntaxError(SourceLocation const& _location, std::string const& _description) { auto err = make_shared<Error>(Error::Type::SyntaxError); @@ -148,6 +158,13 @@ bool SyntaxChecker::visit(Break const& _breakStatement) return true; } +bool SyntaxChecker::visit(UnaryOperation const& _operation) +{ + if (_operation.getOperator() == Token::Add) + warning(_operation.location(), "Use of unary + is deprecated."); + return true; +} + bool SyntaxChecker::visit(PlaceholderStatement const&) { m_placeholderFound = true; diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index 308e128b..8d7dcdd3 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -32,6 +32,7 @@ namespace solidity * The module that performs syntax analysis on the AST: * - whether continue/break is in a for/while loop. * - whether a modifier contains at least one '_' + * - issues deprecation warnings for unary '+' */ class SyntaxChecker: private ASTConstVisitor { @@ -43,6 +44,7 @@ public: private: /// Adds a new error to the list of errors. + void warning(SourceLocation const& _location, std::string const& _description); void syntaxError(SourceLocation const& _location, std::string const& _description); virtual bool visit(SourceUnit const& _sourceUnit) override; @@ -60,6 +62,8 @@ private: virtual bool visit(Continue const& _continueStatement) override; virtual bool visit(Break const& _breakStatement) override; + virtual bool visit(UnaryOperation const& _operation) override; + virtual bool visit(PlaceholderStatement const& _placeholderStatement) override; ErrorList& m_errors; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 512493cd..38cdc1f8 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -24,8 +24,9 @@ #include <memory> #include <boost/range/adaptor/reversed.hpp> #include <libsolidity/ast/AST.h> -#include <libevmasm/Assembly.h> // needed for inline assembly -#include <libsolidity/inlineasm/AsmCodeGen.h> +#include <libsolidity/inlineasm/AsmAnalysis.h> +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> +#include <libsolidity/inlineasm/AsmData.h> using namespace std; using namespace dev; @@ -64,8 +65,10 @@ bool TypeChecker::visit(ContractDefinition const& _contract) { m_scope = &_contract; - // We force our own visiting order here. - //@TODO structs will be visited again below, but it is probably fine. + // We force our own visiting order here. The structs have to be excluded below. + set<ASTNode const*> visited; + for (auto const& s: _contract.definedStructs()) + visited.insert(s); ASTNode::listAccept(_contract.definedStructs(), *this); ASTNode::listAccept(_contract.baseContracts(), *this); @@ -113,7 +116,9 @@ bool TypeChecker::visit(ContractDefinition const& _contract) _contract.annotation().isFullyImplemented = false; } - ASTNode::listAccept(_contract.subNodes(), *this); + for (auto const& n: _contract.subNodes()) + if (!visited.count(n.get())) + n->accept(*this); checkContractExternalTypeClashes(_contract); // check for hash collisions in function signatures @@ -183,13 +188,20 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont using FunTypeAndFlag = std::pair<FunctionTypePointer, bool>; map<string, vector<FunTypeAndFlag>> functions; + bool allBaseConstructorsImplemented = true; // Search from base to derived for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts)) for (FunctionDefinition const* function: contract->definedFunctions()) { // Take constructors out of overload hierarchy if (function->isConstructor()) + { + if (!function->isImplemented()) + // Base contract's constructor is not fully implemented, no way to get + // out of this. + allBaseConstructorsImplemented = false; continue; + } auto& overloads = functions[function->name()]; FunctionTypePointer funType = make_shared<FunctionType>(*function); auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag) @@ -207,6 +219,9 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont it->second = true; } + if (!allBaseConstructorsImplemented) + _contract.annotation().isFullyImplemented = false; + // Set to not fully implemented if at least one flag is false. for (auto const& it: functions) for (auto const& funAndFlag: it.second) @@ -354,6 +369,9 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name())); solAssert(base, "Base contract not available."); + if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + typeError(_inheritance.location(), "Interfaces cannot inherit."); + if (base->isLibrary()) typeError(_inheritance.location(), "Libraries cannot be inherited from."); @@ -396,6 +414,9 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) bool TypeChecker::visit(StructDefinition const& _struct) { + if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + typeError(_struct.location(), "Structs cannot be defined in interfaces."); + for (ASTPointer<VariableDeclaration> const& member: _struct.members()) if (!type(*member)->canBeStored()) typeError(member->location(), "Type cannot be used in struct."); @@ -451,6 +472,15 @@ bool TypeChecker::visit(FunctionDefinition const& _function) dynamic_cast<ContractDefinition const&>(*_function.scope()).annotation().linearizedBaseContracts : vector<ContractDefinition const*>() ); + if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + { + if (_function.isImplemented()) + typeError(_function.location(), "Functions in interfaces cannot have an implementation."); + if (_function.visibility() < FunctionDefinition::Visibility::Public) + typeError(_function.location(), "Functions in interfaces cannot be internal or private."); + if (_function.isConstructor()) + typeError(_function.location(), "Constructor cannot be defined in interfaces."); + } if (_function.isImplemented()) _function.body().accept(*this); return false; @@ -458,6 +488,9 @@ bool TypeChecker::visit(FunctionDefinition const& _function) bool TypeChecker::visit(VariableDeclaration const& _variable) { + if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + typeError(_variable.location(), "Variables cannot be declared in interfaces."); + // Variables can be declared without type (with "var"), in which case the first assignment // sets the type. // Note that assignments before the first declaration are legal because of the special scoping @@ -504,6 +537,13 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) return false; } +bool TypeChecker::visit(EnumDefinition const& _enum) +{ + if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + typeError(_enum.location(), "Enumerable cannot be declared in interfaces."); + return false; +} + void TypeChecker::visitManually( ModifierInvocation const& _modifier, vector<ContractDefinition const*> const& _bases @@ -582,72 +622,98 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) void TypeChecker::endVisit(FunctionTypeName const& _funType) { FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type); - if (fun.location() == FunctionType::Location::External) + if (fun.kind() == FunctionType::Kind::External) if (!fun.canBeUsedExternally(false)) typeError(_funType.location(), "External function type uses internal types."); } bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) { - // Inline assembly does not have its own type-checking phase, so we just run the - // code-generator and see whether it produces any errors. // External references have already been resolved in a prior stage and stored in the annotation. - auto identifierAccess = [&]( + // We run the resolve step again regardless. + assembly::ExternalIdentifierAccess::Resolver identifierAccess = [&]( assembly::Identifier const& _identifier, - eth::Assembly& _assembly, - assembly::CodeGenerator::IdentifierContext _context + assembly::IdentifierContext _context ) { auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); if (ref == _inlineAssembly.annotation().externalReferences.end()) - return false; - Declaration const* declaration = ref->second; + return size_t(-1); + Declaration const* declaration = ref->second.declaration; solAssert(!!declaration, ""); - if (_context == assembly::CodeGenerator::IdentifierContext::RValue) + if (auto var = dynamic_cast<VariableDeclaration const*>(declaration)) { - solAssert(!!declaration->type(), "Type of declaration required but not yet determined."); - unsigned pushes = 0; - if (dynamic_cast<FunctionDefinition const*>(declaration)) - pushes = 1; - else if (auto var = dynamic_cast<VariableDeclaration const*>(declaration)) + if (ref->second.isSlot || ref->second.isOffset) { - if (var->isConstant()) - fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly."); - if (var->isLocalVariable()) - pushes = var->type()->sizeOnStack(); - else if (!var->type()->isValueType()) - pushes = 1; - else - pushes = 2; // slot number, intra slot offset + if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage)) + { + typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables."); + return size_t(-1); + } + else if (_context != assembly::IdentifierContext::RValue) + { + typeError(_identifier.location, "Storage variables cannot be assigned to."); + return size_t(-1); + } } - else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration)) + else if (var->isConstant()) { - if (!contract->isLibrary()) - return false; - pushes = 1; + typeError(_identifier.location, "Constant variables not supported by inline assembly."); + return size_t(-1); + } + else if (!var->isLocalVariable()) + { + typeError(_identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes."); + return size_t(-1); + } + else if (var->type()->dataStoredIn(DataLocation::Storage)) + { + typeError(_identifier.location, "You have to use the _slot or _offset prefix to access storage reference variables."); + return size_t(-1); + } + else if (var->type()->sizeOnStack() != 1) + { + typeError(_identifier.location, "Only types that use one stack slot are supported."); + return size_t(-1); } - else - return false; - for (unsigned i = 0; i < pushes; ++i) - _assembly.append(u256(0)); // just to verify the stack height } - else + else if (_context == assembly::IdentifierContext::LValue) + { + typeError(_identifier.location, "Only local variables can be assigned to in inline assembly."); + return size_t(-1); + } + + if (_context == assembly::IdentifierContext::RValue) { - // lvalue context - if (auto varDecl = dynamic_cast<VariableDeclaration const*>(declaration)) + solAssert(!!declaration->type(), "Type of declaration required but not yet determined."); + if (dynamic_cast<FunctionDefinition const*>(declaration)) { - if (!varDecl->isLocalVariable()) - return false; // only local variables are inline-assemlby lvalues - for (unsigned i = 0; i < declaration->type()->sizeOnStack(); ++i) - _assembly.append(Instruction::POP); // remove value just to verify the stack height + } + else if (dynamic_cast<VariableDeclaration const*>(declaration)) + { + } + else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration)) + { + if (!contract->isLibrary()) + { + typeError(_identifier.location, "Expected a library."); + return size_t(-1); + } } else - return false; + return size_t(-1); } - return true; + ref->second.valueSize = 1; + return size_t(1); }; - assembly::CodeGenerator codeGen(_inlineAssembly.operations(), m_errors); - if (!codeGen.typeCheck(identifierAccess)) + solAssert(!_inlineAssembly.annotation().analysisInfo, ""); + _inlineAssembly.annotation().analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); + assembly::AsmAnalyzer analyzer( + *_inlineAssembly.annotation().analysisInfo, + m_errors, + identifierAccess + ); + if (!analyzer.analyze(_inlineAssembly.operations())) return false; return true; } @@ -886,15 +952,14 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement) { if (auto callType = dynamic_cast<FunctionType const*>(type(call->expression()).get())) { - using Location = FunctionType::Location; - Location location = callType->location(); + auto kind = callType->kind(); if ( - location == Location::Bare || - location == Location::BareCallCode || - location == Location::BareDelegateCall + kind == FunctionType::Kind::Bare || + kind == FunctionType::Kind::BareCallCode || + kind == FunctionType::Kind::BareDelegateCall ) warning(_statement.location(), "Return value of low-level calls not used."); - else if (location == Location::Send) + else if (kind == FunctionType::Kind::Send) warning(_statement.location(), "Failure condition of 'send' ignored. Consider using 'transfer' instead."); } } @@ -1387,7 +1452,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) TypePointers{type}, strings(), strings(), - FunctionType::Location::ObjectCreation + FunctionType::Kind::ObjectCreation ); _newExpression.annotation().isPure = true; } @@ -1636,8 +1701,8 @@ bool TypeChecker::visit(Identifier const& _identifier) if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration)) annotation.isPure = annotation.isConstant = variableDeclaration->isConstant(); else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration)) - if (auto functionType = dynamic_cast<FunctionType const*>(annotation.type.get())) - annotation.isPure = functionType->isPure(); + if (dynamic_cast<FunctionType const*>(annotation.type.get())) + annotation.isPure = true; return false; } diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 46d8230a..88559f44 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -83,6 +83,7 @@ private: virtual bool visit(StructDefinition const& _struct) override; virtual bool visit(FunctionDefinition const& _function) override; virtual bool visit(VariableDeclaration const& _variable) override; + virtual bool visit(EnumDefinition const& _enum) override; /// We need to do this manually because we want to pass the bases of the current contract in /// case this is a base constructor call. void visitManually(ModifierInvocation const& _modifier, std::vector<ContractDefinition const*> const& _bases); diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 8031760d..02234ffc 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -316,19 +316,21 @@ protected: class ContractDefinition: public Declaration, public Documented { public: + enum class ContractKind { Interface, Contract, Library }; + ContractDefinition( SourceLocation const& _location, ASTPointer<ASTString> const& _name, ASTPointer<ASTString> const& _documentation, std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts, std::vector<ASTPointer<ASTNode>> const& _subNodes, - bool _isLibrary + ContractKind _contractKind = ContractKind::Contract ): Declaration(_location, _name), Documented(_documentation), m_baseContracts(_baseContracts), m_subNodes(_subNodes), - m_isLibrary(_isLibrary) + m_contractKind(_contractKind) {} virtual void accept(ASTVisitor& _visitor) override; @@ -344,7 +346,7 @@ public: std::vector<FunctionDefinition const*> definedFunctions() const { return filteredNodes<FunctionDefinition>(m_subNodes); } std::vector<EventDefinition const*> events() const { return filteredNodes<EventDefinition>(m_subNodes); } std::vector<EventDefinition const*> const& interfaceEvents() const; - bool isLibrary() const { return m_isLibrary; } + bool isLibrary() const { return m_contractKind == ContractKind::Library; } /// @returns a map of canonical function signatures to FunctionDefinitions /// as intended for use by the ABI. @@ -371,10 +373,12 @@ public: virtual ContractDefinitionAnnotation& annotation() const override; + ContractKind contractKind() const { return m_contractKind; } + private: std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts; std::vector<ASTPointer<ASTNode>> m_subNodes; - bool m_isLibrary; + ContractKind m_contractKind; // parsed Natspec documentation of the contract. Json::Value m_userDocumentation; diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index bd297f9e..a7d89248 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -22,11 +22,12 @@ #pragma once +#include <libsolidity/ast/ASTForward.h> + #include <map> #include <memory> #include <vector> #include <set> -#include <libsolidity/ast/ASTForward.h> namespace dev { @@ -112,13 +113,24 @@ struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation namespace assembly { -struct Identifier; // forward + struct AsmAnalysisInfo; + struct Identifier; } struct InlineAssemblyAnnotation: StatementAnnotation { - /// Mapping containing resolved references to external identifiers. - std::map<assembly::Identifier const*, Declaration const*> externalReferences; + struct ExternalIdentifierInfo + { + Declaration const* declaration = nullptr; + bool isSlot = false; ///< Whether the storage slot of a variable is queried. + bool isOffset = false; ///< Whether the intra-slot offset of a storage variable is queried. + size_t valueSize = size_t(-1); + }; + + /// Mapping containing resolved references to external identifiers and their value size + std::map<assembly::Identifier const*, ExternalIdentifierInfo> externalReferences; + /// Information generated during analysis phase. + std::shared_ptr<assembly::AsmAnalysisInfo> analysisInfo; }; struct ReturnAnnotation: StatementAnnotation diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 69c10c8d..9ea23687 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -40,6 +40,21 @@ void ASTJsonConverter::addJsonNode( bool _hasChildren = false ) { + ASTJsonConverter::addJsonNode( + _node, + _nodeName, + std::vector<pair<string const, Json::Value const>>(_attributes), + _hasChildren + ); +} + +void ASTJsonConverter::addJsonNode( + ASTNode const& _node, + string const& _nodeName, + std::vector<pair<string const, Json::Value const>> const& _attributes, + bool _hasChildren = false +) +{ Json::Value node; node["id"] = Json::UInt64(_node.id()); @@ -183,11 +198,18 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node) bool ASTJsonConverter::visit(VariableDeclaration const& _node) { - addJsonNode(_node, "VariableDeclaration", { + std::vector<pair<string const, Json::Value const>> attributes = { make_pair("name", _node.name()), - make_pair("type", type(_node)) - }, true); + make_pair("type", type(_node)), + make_pair("constant", _node.isConstant()), + make_pair("storageLocation", location(_node.referenceLocation())), + make_pair("visibility", visibility(_node.visibility())) + }; + if (m_inEvent) + attributes.push_back(make_pair("indexed", _node.isIndexed())); + addJsonNode(_node, "VariableDeclaration", attributes, true); return true; + } bool ASTJsonConverter::visit(ModifierDefinition const& _node) @@ -209,6 +231,7 @@ bool ASTJsonConverter::visit(TypeName const&) bool ASTJsonConverter::visit(EventDefinition const& _node) { + m_inEvent = true; addJsonNode(_node, "EventDefinition", { make_pair("name", _node.name()) }, true); return true; } @@ -502,6 +525,7 @@ void ASTJsonConverter::endVisit(ModifierInvocation const&) void ASTJsonConverter::endVisit(EventDefinition const&) { + m_inEvent = false; goUp(); } @@ -670,6 +694,21 @@ string ASTJsonConverter::visibility(Declaration::Visibility const& _visibility) } } +string ASTJsonConverter::location(VariableDeclaration::Location _location) +{ + switch (_location) + { + case VariableDeclaration::Location::Default: + return "default"; + case VariableDeclaration::Location::Storage: + return "storage"; + case VariableDeclaration::Location::Memory: + return "memory"; + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown declaration location.")); + } +} + string ASTJsonConverter::type(Expression const& _expression) { return _expression.annotation().type ? _expression.annotation().type->toString() : "Unknown"; diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 49f23f99..bd5e6560 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -151,8 +151,15 @@ private: std::initializer_list<std::pair<std::string const, Json::Value const>> _attributes, bool _hasChildren ); + void addJsonNode( + ASTNode const& _node, + std::string const& _nodeName, + std::vector<std::pair<std::string const, Json::Value const>> const& _attributes, + bool _hasChildren + ); std::string sourceLocationToString(SourceLocation const& _location) const; std::string visibility(Declaration::Visibility const& _visibility); + std::string location(VariableDeclaration::Location _location); std::string type(Expression const& _expression); std::string type(VariableDeclaration const& _varDecl); inline void goUp() @@ -161,6 +168,7 @@ private: m_jsonNodePtrs.pop(); } + bool m_inEvent = false; ///< whether we are currently inside an event or not bool processed = false; Json::Value m_astJson; std::stack<Json::Value*> m_jsonNodePtrs; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index e7f53422..41ee6498 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -462,11 +462,11 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons if (isAddress()) return { {"balance", make_shared<IntegerType >(256)}, - {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::Bare, true, false, true)}, - {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareCallCode, true, false, true)}, - {"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareDelegateCall, true)}, - {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Location::Send)}, - {"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Location::Transfer)} + {"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::Bare, true, false, true)}, + {"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCallCode, true, false, true)}, + {"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareDelegateCall, true)}, + {"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)}, + {"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)} }; else return MemberList::MemberMap(); @@ -1466,7 +1466,7 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const TypePointers{make_shared<IntegerType>(256)}, strings{string()}, strings{string()}, - isByteArray() ? FunctionType::Location::ByteArrayPush : FunctionType::Location::ArrayPush + isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush )}); } return members; @@ -1766,7 +1766,7 @@ FunctionTypePointer StructType::constructorType() const TypePointers{copyForLocation(DataLocation::Memory, false)}, paramNames, strings(), - FunctionType::Location::Internal + FunctionType::Kind::Internal ); } @@ -1967,7 +1967,7 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons } FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal): - m_location(_isInternal ? Location::Internal : Location::External), + m_kind(_isInternal ? Kind::Internal : Kind::External), m_isConstant(_function.isDeclaredConst()), m_isPayable(_isInternal ? false : _function.isPayable()), m_declaration(&_function) @@ -1998,7 +1998,7 @@ FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal } FunctionType::FunctionType(VariableDeclaration const& _varDecl): - m_location(Location::External), m_isConstant(true), m_declaration(&_varDecl) + m_kind(Kind::External), m_isConstant(true), m_declaration(&_varDecl) { TypePointers paramTypes; vector<string> paramNames; @@ -2058,7 +2058,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): } FunctionType::FunctionType(EventDefinition const& _event): - m_location(Location::Event), m_isConstant(true), m_declaration(&_event) + m_kind(Kind::Event), m_isConstant(true), m_declaration(&_event) { TypePointers params; vector<string> paramNames; @@ -2074,19 +2074,19 @@ FunctionType::FunctionType(EventDefinition const& _event): } FunctionType::FunctionType(FunctionTypeName const& _typeName): - m_location(_typeName.visibility() == VariableDeclaration::Visibility::External ? Location::External : Location::Internal), + m_kind(_typeName.visibility() == VariableDeclaration::Visibility::External ? Kind::External : Kind::Internal), m_isConstant(_typeName.isDeclaredConst()), m_isPayable(_typeName.isPayable()) { if (_typeName.isPayable()) { - solAssert(m_location == Location::External, "Internal payable function type used."); + solAssert(m_kind == Kind::External, "Internal payable function type used."); solAssert(!m_isConstant, "Payable constant function"); } for (auto const& t: _typeName.parameterTypes()) { solAssert(t->annotation().type, "Type not set for parameter."); - if (m_location == Location::External) + if (m_kind == Kind::External) solAssert( t->annotation().type->canBeUsedExternally(false), "Internal type used as parameter for external function." @@ -2096,7 +2096,7 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName): for (auto const& t: _typeName.returnParameterTypes()) { solAssert(t->annotation().type, "Type not set for return parameter."); - if (m_location == Location::External) + if (m_kind == Kind::External) solAssert( t->annotation().type->canBeUsedExternally(false), "Internal type used as return parameter for external function." @@ -2126,7 +2126,7 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c TypePointers{make_shared<ContractType>(_contract)}, parameterNames, strings{""}, - Location::Creation, + Kind::Creation, false, nullptr, false, @@ -2151,38 +2151,38 @@ TypePointers FunctionType::parameterTypes() const string FunctionType::identifier() const { string id = "t_function_"; - switch (location()) + switch (m_kind) { - case Location::Internal: id += "internal"; break; - case Location::External: id += "external"; break; - case Location::CallCode: id += "callcode"; break; - case Location::DelegateCall: id += "delegatecall"; break; - case Location::Bare: id += "bare"; break; - case Location::BareCallCode: id += "barecallcode"; break; - case Location::BareDelegateCall: id += "baredelegatecall"; break; - case Location::Creation: id += "creation"; break; - case Location::Send: id += "send"; break; - case Location::Transfer: id += "transfer"; break; - case Location::SHA3: id += "sha3"; break; - case Location::Selfdestruct: id += "selfdestruct"; break; - case Location::Revert: id += "revert"; break; - case Location::ECRecover: id += "ecrecover"; break; - case Location::SHA256: id += "sha256"; break; - case Location::RIPEMD160: id += "ripemd160"; break; - case Location::Log0: id += "log0"; break; - case Location::Log1: id += "log1"; break; - case Location::Log2: id += "log2"; break; - case Location::Log3: id += "log3"; break; - case Location::Log4: id += "log4"; break; - case Location::Event: id += "event"; break; - case Location::SetGas: id += "setgas"; break; - case Location::SetValue: id += "setvalue"; break; - case Location::BlockHash: id += "blockhash"; break; - case Location::AddMod: id += "addmod"; break; - case Location::MulMod: id += "mulmod"; break; - case Location::ArrayPush: id += "arraypush"; break; - case Location::ByteArrayPush: id += "bytearraypush"; break; - case Location::ObjectCreation: id += "objectcreation"; break; + case Kind::Internal: id += "internal"; break; + case Kind::External: id += "external"; break; + case Kind::CallCode: id += "callcode"; break; + case Kind::DelegateCall: id += "delegatecall"; break; + case Kind::Bare: id += "bare"; break; + case Kind::BareCallCode: id += "barecallcode"; break; + case Kind::BareDelegateCall: id += "baredelegatecall"; break; + case Kind::Creation: id += "creation"; break; + case Kind::Send: id += "send"; break; + case Kind::Transfer: id += "transfer"; break; + case Kind::SHA3: id += "sha3"; break; + case Kind::Selfdestruct: id += "selfdestruct"; break; + case Kind::Revert: id += "revert"; break; + case Kind::ECRecover: id += "ecrecover"; break; + case Kind::SHA256: id += "sha256"; break; + case Kind::RIPEMD160: id += "ripemd160"; break; + case Kind::Log0: id += "log0"; break; + case Kind::Log1: id += "log1"; break; + case Kind::Log2: id += "log2"; break; + case Kind::Log3: id += "log3"; break; + case Kind::Log4: id += "log4"; break; + case Kind::Event: id += "event"; break; + case Kind::SetGas: id += "setgas"; break; + case Kind::SetValue: id += "setvalue"; break; + case Kind::BlockHash: id += "blockhash"; break; + case Kind::AddMod: id += "addmod"; break; + case Kind::MulMod: id += "mulmod"; break; + case Kind::ArrayPush: id += "arraypush"; break; + case Kind::ByteArrayPush: id += "bytearraypush"; break; + case Kind::ObjectCreation: id += "objectcreation"; break; default: solAssert(false, "Unknown function location."); break; } if (isConstant()) @@ -2203,7 +2203,7 @@ bool FunctionType::operator==(Type const& _other) const return false; FunctionType const& other = dynamic_cast<FunctionType const&>(_other); - if (m_location != other.m_location) + if (m_kind != other.m_kind) return false; if (m_isConstant != other.isConstant()) return false; @@ -2231,7 +2231,7 @@ bool FunctionType::operator==(Type const& _other) const bool FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - if (m_location == Location::External && _convertTo.category() == Category::Integer) + if (m_kind == Kind::External && _convertTo.category() == Category::Integer) { IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo); if (convertTo.isAddress()) @@ -2249,7 +2249,7 @@ TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const string FunctionType::canonicalName(bool) const { - solAssert(m_location == Location::External, ""); + solAssert(m_kind == Kind::External, ""); return "function"; } @@ -2263,7 +2263,7 @@ string FunctionType::toString(bool _short) const name += " constant"; if (m_isPayable) name += " payable"; - if (m_location == Location::External) + if (m_kind == Kind::External) name += " external"; if (!m_returnParameterTypes.empty()) { @@ -2285,7 +2285,7 @@ unsigned FunctionType::calldataEncodedSize(bool _padded) const u256 FunctionType::storageSize() const { - if (m_location == Location::External || m_location == Location::Internal) + if (m_kind == Kind::External || m_kind == Kind::Internal) return 1; else BOOST_THROW_EXCEPTION( @@ -2295,9 +2295,9 @@ u256 FunctionType::storageSize() const unsigned FunctionType::storageBytes() const { - if (m_location == Location::External) + if (m_kind == Kind::External) return 20 + 4; - else if (m_location == Location::Internal) + else if (m_kind == Kind::Internal) return 8; // it should really not be possible to create larger programs else BOOST_THROW_EXCEPTION( @@ -2307,21 +2307,21 @@ unsigned FunctionType::storageBytes() const unsigned FunctionType::sizeOnStack() const { - Location location = m_location; - if (m_location == Location::SetGas || m_location == Location::SetValue) + Kind kind = m_kind; + if (m_kind == Kind::SetGas || m_kind == Kind::SetValue) { solAssert(m_returnParameterTypes.size() == 1, ""); - location = dynamic_cast<FunctionType const&>(*m_returnParameterTypes.front()).m_location; + kind = dynamic_cast<FunctionType const&>(*m_returnParameterTypes.front()).m_kind; } unsigned size = 0; - if (location == Location::External || location == Location::CallCode || location == Location::DelegateCall) + if (kind == Kind::External || kind == Kind::CallCode || kind == Kind::DelegateCall) size = 2; - else if (location == Location::Bare || location == Location::BareCallCode || location == Location::BareDelegateCall) + else if (kind == Kind::Bare || kind == Kind::BareCallCode || kind == Kind::BareDelegateCall) size = 1; - else if (location == Location::Internal) + else if (kind == Kind::Internal) size = 1; - else if (location == Location::ArrayPush || location == Location::ByteArrayPush) + else if (kind == Kind::ArrayPush || kind == Kind::ByteArrayPush) size = 1; if (m_gasSet) size++; @@ -2362,26 +2362,26 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const return make_shared<FunctionType>( paramTypes, retParamTypes, m_parameterNames, m_returnParameterNames, - m_location, m_arbitraryParameters, + m_kind, m_arbitraryParameters, m_declaration, m_isConstant, m_isPayable ); } MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) const { - switch (m_location) + switch (m_kind) { - case Location::External: - case Location::Creation: - case Location::ECRecover: - case Location::SHA256: - case Location::RIPEMD160: - case Location::Bare: - case Location::BareCallCode: - case Location::BareDelegateCall: + case Kind::External: + case Kind::Creation: + case Kind::ECRecover: + case Kind::SHA256: + case Kind::RIPEMD160: + case Kind::Bare: + case Kind::BareCallCode: + case Kind::BareDelegateCall: { MemberList::MemberMap members; - if (m_location != Location::BareDelegateCall && m_location != Location::DelegateCall) + if (m_kind != Kind::BareDelegateCall && m_kind != Kind::DelegateCall) { if (m_isPayable) members.push_back(MemberList::Member( @@ -2391,7 +2391,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con TypePointers{copyAndSetGasOrValue(false, true)}, strings(), strings(), - Location::SetValue, + Kind::SetValue, false, nullptr, false, @@ -2401,7 +2401,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con ) )); } - if (m_location != Location::Creation) + if (m_kind != Kind::Creation) members.push_back(MemberList::Member( "gas", make_shared<FunctionType>( @@ -2409,7 +2409,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con TypePointers{copyAndSetGasOrValue(true, false)}, strings(), strings(), - Location::SetGas, + Kind::SetGas, false, nullptr, false, @@ -2428,7 +2428,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con TypePointer FunctionType::encodingType() const { // Only external functions can be encoded, internal functions cannot leave code boundaries. - if (m_location == Location::External) + if (m_kind == Kind::External) return shared_from_this(); else return TypePointer(); @@ -2436,7 +2436,7 @@ TypePointer FunctionType::encodingType() const TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const { - if (m_location == Location::External) + if (m_kind == Kind::External) return shared_from_this(); else return TypePointer(); @@ -2478,14 +2478,14 @@ bool FunctionType::hasEqualArgumentTypes(FunctionType const& _other) const bool FunctionType::isBareCall() const { - switch (m_location) + switch (m_kind) { - case Location::Bare: - case Location::BareCallCode: - case Location::BareDelegateCall: - case Location::ECRecover: - case Location::SHA256: - case Location::RIPEMD160: + case Kind::Bare: + case Kind::BareCallCode: + case Kind::BareDelegateCall: + case Kind::ECRecover: + case Kind::SHA256: + case Kind::RIPEMD160: return true; default: return false; @@ -2520,13 +2520,13 @@ u256 FunctionType::externalIdentifier() const bool FunctionType::isPure() const { return - m_location == Location::SHA3 || - m_location == Location::ECRecover || - m_location == Location::SHA256 || - m_location == Location::RIPEMD160 || - m_location == Location::AddMod || - m_location == Location::MulMod || - m_location == Location::ObjectCreation; + m_kind == Kind::SHA3 || + m_kind == Kind::ECRecover || + m_kind == Kind::SHA256 || + m_kind == Kind::RIPEMD160 || + m_kind == Kind::AddMod || + m_kind == Kind::MulMod || + m_kind == Kind::ObjectCreation; } TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) @@ -2545,7 +2545,7 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con m_returnParameterTypes, m_parameterNames, m_returnParameterNames, - m_location, + m_kind, m_arbitraryParameters, m_declaration, m_isConstant, @@ -2571,18 +2571,18 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound) parameterTypes.push_back(t); } - Location location = m_location; + Kind kind = m_kind; if (_inLibrary) { solAssert(!!m_declaration, "Declaration has to be available."); if (!m_declaration->isPublic()) - location = Location::Internal; // will be inlined + kind = Kind::Internal; // will be inlined else - location = Location::DelegateCall; + kind = Kind::DelegateCall; } TypePointers returnParameterTypes = m_returnParameterTypes; - if (location != Location::Internal) + if (kind != Kind::Internal) { // Alter dynamic types to be non-accessible. for (auto& param: returnParameterTypes) @@ -2595,7 +2595,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound) returnParameterTypes, m_parameterNames, m_returnParameterNames, - location, + kind, m_arbitraryParameters, m_declaration, m_isConstant, @@ -2821,7 +2821,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const return MemberList::MemberMap({ {"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, {"timestamp", make_shared<IntegerType>(256)}, - {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Location::BlockHash)}, + {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash)}, {"difficulty", make_shared<IntegerType>(256)}, {"number", make_shared<IntegerType>(256)}, {"gaslimit", make_shared<IntegerType>(256)} diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 78326aa6..c4ffc44c 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -817,8 +817,7 @@ class FunctionType: public Type { public: /// How this function is invoked on the EVM. - /// @todo This documentation is outdated, and Location should rather be named "Type" - enum class Location + enum class Kind { Internal, ///< stack-call using plain JUMP External, ///< external call using CALL @@ -868,7 +867,7 @@ public: FunctionType( strings const& _parameterTypes, strings const& _returnParameterTypes, - Location _location = Location::Internal, + Kind _kind = Kind::Internal, bool _arbitraryParameters = false, bool _constant = false, bool _payable = false @@ -877,7 +876,7 @@ public: parseElementaryTypeVector(_returnParameterTypes), strings(), strings(), - _location, + _kind, _arbitraryParameters, nullptr, _constant, @@ -895,7 +894,7 @@ public: TypePointers const& _returnParameterTypes, strings _parameterNames = strings(), strings _returnParameterNames = strings(), - Location _location = Location::Internal, + Kind _kind = Kind::Internal, bool _arbitraryParameters = false, Declaration const* _declaration = nullptr, bool _isConstant = false, @@ -908,7 +907,7 @@ public: m_returnParameterTypes(_returnParameterTypes), m_parameterNames(_parameterNames), m_returnParameterNames(_returnParameterNames), - m_location(_location), + m_kind(_kind), m_arbitraryParameters(_arbitraryParameters), m_gasSet(_gasSet), m_valueSet(_valueSet), @@ -937,11 +936,11 @@ public: virtual std::string canonicalName(bool /*_addDataLocation*/) const override; virtual std::string toString(bool _short) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; - virtual bool canBeStored() const override { return m_location == Location::Internal || m_location == Location::External; } + virtual bool canBeStored() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } virtual u256 storageSize() const override; virtual unsigned storageBytes() const override; virtual bool isValueType() const override { return true; } - virtual bool canLiveOutsideStorage() const override { return m_location == Location::Internal || m_location == Location::External; } + virtual bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } virtual unsigned sizeOnStack() const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual TypePointer encodingType() const override; @@ -964,7 +963,7 @@ public: /// @returns true if the ABI is used for this call (only meaningful for external calls) bool isBareCall() const; - Location const& location() const { return m_location; } + Kind const& kind() const { return m_kind; } /// @returns the external signature of this function type given the function name std::string externalSignature() const; /// @returns the external identifier of this function (the hash of the signature). @@ -986,7 +985,7 @@ public: ASTPointer<ASTString> documentation() const; /// true iff arguments are to be padded to multiples of 32 bytes for external calls - bool padArguments() const { return !(m_location == Location::SHA3 || m_location == Location::SHA256 || m_location == Location::RIPEMD160); } + bool padArguments() const { return !(m_kind == Kind::SHA3 || m_kind == Kind::SHA256 || m_kind == Kind::RIPEMD160); } bool takesArbitraryParameters() const { return m_arbitraryParameters; } bool gasSet() const { return m_gasSet; } bool valueSet() const { return m_valueSet; } @@ -1012,7 +1011,7 @@ private: TypePointers m_returnParameterTypes; std::vector<std::string> m_parameterNames; std::vector<std::string> m_returnParameterNames; - Location const m_location; + Kind const m_kind; /// true if the function takes an arbitrary number of arguments of arbitrary types bool const m_arbitraryParameters = false; bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index a8316109..51dd9fd2 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -265,31 +265,40 @@ void CompilerContext::appendInlineAssembly( } unsigned startStackHeight = stackHeight(); - auto identifierAccess = [&]( + + assembly::ExternalIdentifierAccess identifierAccess; + identifierAccess.resolve = [&]( + assembly::Identifier const& _identifier, + assembly::IdentifierContext + ) + { + auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name); + return it == _localVariables.end() ? size_t(-1) : 1; + }; + identifierAccess.generateCode = [&]( assembly::Identifier const& _identifier, - eth::Assembly& _assembly, - assembly::CodeGenerator::IdentifierContext _context - ) { + assembly::IdentifierContext _context, + eth::Assembly& _assembly + ) + { auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name); - if (it == _localVariables.end()) - return false; + solAssert(it != _localVariables.end(), ""); unsigned stackDepth = _localVariables.end() - it; int stackDiff = _assembly.deposit() - startStackHeight + stackDepth; - if (_context == assembly::CodeGenerator::IdentifierContext::LValue) + if (_context == assembly::IdentifierContext::LValue) stackDiff -= 1; if (stackDiff < 1 || stackDiff > 16) BOOST_THROW_EXCEPTION( CompilerError() << errinfo_comment("Stack too deep, try removing local variables.") ); - if (_context == assembly::CodeGenerator::IdentifierContext::RValue) + if (_context == assembly::IdentifierContext::RValue) _assembly.append(dupInstruction(stackDiff)); else { _assembly.append(swapInstruction(stackDiff)); _assembly.append(Instruction::POP); } - return true; }; solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), "Failed to assemble inline assembly block."); diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 42323abd..dc0b340c 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -135,7 +135,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound } else if ( _type.category() == Type::Category::Function && - dynamic_cast<FunctionType const&>(_type).location() == FunctionType::Location::External + dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External ) { solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); @@ -795,7 +795,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp IntegerType const& targetType = dynamic_cast<IntegerType const&>(_targetType); solAssert(targetType.isAddress(), "Function type can only be converted to address."); FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack); - solAssert(typeOnStack.location() == FunctionType::Location::External, "Only external function type can be converted."); + solAssert(typeOnStack.kind() == FunctionType::Kind::External, "Only external function type can be converted."); // stack: <address> <function_id> m_context << Instruction::POP; @@ -820,7 +820,7 @@ void CompilerUtils::pushZeroValue(Type const& _type) { if (auto const* funType = dynamic_cast<FunctionType const*>(&_type)) { - if (funType->location() == FunctionType::Location::Internal) + if (funType->kind() == FunctionType::Kind::Internal) { m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) { _context.appendInvalid(); @@ -983,7 +983,7 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda unsigned numBytes = _type.calldataEncodedSize(_padToWords); bool isExternalFunctionType = false; if (auto const* funType = dynamic_cast<FunctionType const*>(&_type)) - if (funType->location() == FunctionType::Location::External) + if (funType->kind() == FunctionType::Kind::External) isExternalFunctionType = true; if (numBytes == 0) { diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 6524bd03..34ef13c0 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -520,93 +520,129 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) { ErrorList errors; - assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errors); + assembly::CodeGenerator codeGen(errors); unsigned startStackHeight = m_context.stackHeight(); - codeGen.assemble( - m_context.nonConstAssembly(), - [&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) { - auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); - if (ref == _inlineAssembly.annotation().externalReferences.end()) - return false; - Declaration const* decl = ref->second; - solAssert(!!decl, ""); - if (_context == assembly::CodeGenerator::IdentifierContext::RValue) + assembly::ExternalIdentifierAccess identifierAccess; + identifierAccess.resolve = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext) + { + auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); + if (ref == _inlineAssembly.annotation().externalReferences.end()) + return size_t(-1); + return ref->second.valueSize; + }; + identifierAccess.generateCode = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext _context, eth::Assembly& _assembly) + { + auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); + solAssert(ref != _inlineAssembly.annotation().externalReferences.end(), ""); + Declaration const* decl = ref->second.declaration; + solAssert(!!decl, ""); + if (_context == assembly::IdentifierContext::RValue) + { + int const depositBefore = _assembly.deposit(); + solAssert(!!decl->type(), "Type of declaration required but not yet determined."); + if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl)) { - solAssert(!!decl->type(), "Type of declaration required but not yet determined."); - if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl)) + solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); + functionDef = &m_context.resolveVirtualFunction(*functionDef); + _assembly.append(m_context.functionEntryLabel(*functionDef).pushTag()); + // If there is a runtime context, we have to merge both labels into the same + // stack slot in case we store it in storage. + if (CompilerContext* rtc = m_context.runtimeContext()) { - functionDef = &m_context.resolveVirtualFunction(*functionDef); - _assembly.append(m_context.functionEntryLabel(*functionDef).pushTag()); - // If there is a runtime context, we have to merge both labels into the same - // stack slot in case we store it in storage. - if (CompilerContext* rtc = m_context.runtimeContext()) - { - _assembly.append(u256(1) << 32); - _assembly.append(Instruction::MUL); - _assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub())); - _assembly.append(Instruction::OR); - } + _assembly.append(u256(1) << 32); + _assembly.append(Instruction::MUL); + _assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub())); + _assembly.append(Instruction::OR); } - else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl)) + } + else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl)) + { + solAssert(!variable->isConstant(), ""); + if (m_context.isStateVariable(decl)) { - solAssert(!variable->isConstant(), ""); - if (m_context.isLocalVariable(variable)) - { - int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable); - if (stackDiff < 1 || stackDiff > 16) - BOOST_THROW_EXCEPTION( - CompilerError() << - errinfo_sourceLocation(_inlineAssembly.location()) << - errinfo_comment("Stack too deep, try removing local variables.") - ); - for (unsigned i = 0; i < variable->type()->sizeOnStack(); ++i) - _assembly.append(dupInstruction(stackDiff)); - } + auto const& location = m_context.storageLocationOfVariable(*decl); + if (ref->second.isSlot) + m_context << location.first; + else if (ref->second.isOffset) + m_context << u256(location.second); else + solAssert(false, ""); + } + else if (m_context.isLocalVariable(decl)) + { + int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable); + if (ref->second.isSlot || ref->second.isOffset) { - solAssert(m_context.isStateVariable(variable), "Invalid variable type."); - auto const& location = m_context.storageLocationOfVariable(*variable); - if (!variable->type()->isValueType()) + solAssert(variable->type()->dataStoredIn(DataLocation::Storage), ""); + unsigned size = variable->type()->sizeOnStack(); + if (size == 2) { - solAssert(location.second == 0, "Intra-slot offest assumed to be zero."); - _assembly.append(location.first); + // slot plus offset + if (ref->second.isOffset) + stackDiff--; } else { - _assembly.append(location.first); - _assembly.append(u256(location.second)); + solAssert(size == 1, ""); + // only slot, offset is zero + if (ref->second.isOffset) + { + _assembly.append(u256(0)); + return; + } } } - } - else if (auto contract = dynamic_cast<ContractDefinition const*>(decl)) - { - solAssert(contract->isLibrary(), ""); - _assembly.appendLibraryAddress(contract->fullyQualifiedName()); + else + solAssert(variable->type()->sizeOnStack() == 1, ""); + if (stackDiff < 1 || stackDiff > 16) + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_sourceLocation(_inlineAssembly.location()) << + errinfo_comment("Stack too deep, try removing local variables.") + ); + solAssert(variable->type()->sizeOnStack() == 1, ""); + _assembly.append(dupInstruction(stackDiff)); } else - solAssert(false, "Invalid declaration type."); - } else { - // lvalue context - auto variable = dynamic_cast<VariableDeclaration const*>(decl); - solAssert( - !!variable && m_context.isLocalVariable(variable), - "Can only assign to stack variables in inline assembly." - ); - unsigned size = variable->type()->sizeOnStack(); - int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - size; - if (stackDiff > 16 || stackDiff < 1) - BOOST_THROW_EXCEPTION( - CompilerError() << - errinfo_sourceLocation(_inlineAssembly.location()) << - errinfo_comment("Stack too deep, try removing local variables.") - ); - for (unsigned i = 0; i < size; ++i) { - _assembly.append(swapInstruction(stackDiff)); - _assembly.append(Instruction::POP); - } + solAssert(false, ""); } - return true; + else if (auto contract = dynamic_cast<ContractDefinition const*>(decl)) + { + solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); + solAssert(contract->isLibrary(), ""); + _assembly.appendLibraryAddress(contract->fullyQualifiedName()); + } + else + solAssert(false, "Invalid declaration type."); + solAssert(_assembly.deposit() - depositBefore == int(ref->second.valueSize), ""); } + else + { + // lvalue context + solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); + auto variable = dynamic_cast<VariableDeclaration const*>(decl); + solAssert( + !!variable && m_context.isLocalVariable(variable), + "Can only assign to stack variables in inline assembly." + ); + solAssert(variable->type()->sizeOnStack() == 1, ""); + int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - 1; + if (stackDiff > 16 || stackDiff < 1) + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_sourceLocation(_inlineAssembly.location()) << + errinfo_comment("Stack too deep, try removing local variables.") + ); + _assembly.append(swapInstruction(stackDiff)); + _assembly.append(Instruction::POP); + } + }; + solAssert(_inlineAssembly.annotation().analysisInfo, ""); + codeGen.assemble( + _inlineAssembly.operations(), + *_inlineAssembly.annotation().analysisInfo, + m_context.nonConstAssembly(), + identifierAccess ); solAssert(Error::containsOnlyWarnings(errors), "Code generation for inline assembly with errors requested."); m_context.setStackOffset(startStackHeight); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 744a80c4..f018b311 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -434,7 +434,6 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { CompilerContext::LocationSetter locationSetter(m_context, _functionCall); - using Location = FunctionType::Location; if (_functionCall.annotation().isTypeConversion) { solAssert(_functionCall.arguments().size() == 1, ""); @@ -499,10 +498,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) FunctionType const& function = *functionType; if (function.bound()) // Only delegatecall and internal functions can be bound, this might be lifted later. - solAssert(function.location() == Location::DelegateCall || function.location() == Location::Internal, ""); - switch (function.location()) + solAssert(function.kind() == FunctionType::Kind::DelegateCall || function.kind() == FunctionType::Kind::Internal, ""); + switch (function.kind()) { - case Location::Internal: + case FunctionType::Kind::Internal: { // Calling convention: Caller pushes return address and arguments // Callee removes them and pushes return values @@ -538,16 +537,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context.adjustStackOffset(returnParametersSize - parameterSize - 1); break; } - case Location::External: - case Location::CallCode: - case Location::DelegateCall: - case Location::Bare: - case Location::BareCallCode: - case Location::BareDelegateCall: + case FunctionType::Kind::External: + case FunctionType::Kind::CallCode: + case FunctionType::Kind::DelegateCall: + case FunctionType::Kind::Bare: + case FunctionType::Kind::BareCallCode: + case FunctionType::Kind::BareDelegateCall: _functionCall.expression().accept(*this); appendExternalFunctionCall(function, arguments); break; - case Location::Creation: + case FunctionType::Kind::Creation: { _functionCall.expression().accept(*this); solAssert(!function.gasSet(), "Gas limit set for contract creation."); @@ -592,7 +591,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << swapInstruction(1) << Instruction::POP; break; } - case Location::SetGas: + case FunctionType::Kind::SetGas: { // stack layout: contract_address function_id [gas] [value] _functionCall.expression().accept(*this); @@ -608,7 +607,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::POP; break; } - case Location::SetValue: + case FunctionType::Kind::SetValue: // stack layout: contract_address function_id [gas] [value] _functionCall.expression().accept(*this); // Note that function is not the original function, but the ".value" function. @@ -617,8 +616,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::POP; arguments.front()->accept(*this); break; - case Location::Send: - case Location::Transfer: + case FunctionType::Kind::Send: + case FunctionType::Kind::Transfer: _functionCall.expression().accept(*this); // Provide the gas stipend manually at first because we may send zero ether. // Will be zeroed if we send more than zero ether. @@ -637,7 +636,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) TypePointers{}, strings(), strings(), - Location::Bare, + FunctionType::Kind::Bare, false, nullptr, false, @@ -647,24 +646,24 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) ), {} ); - if (function.location() == Location::Transfer) + if (function.kind() == FunctionType::Kind::Transfer) { // Check if zero (out of stack or not enough balance). m_context << Instruction::ISZERO; m_context.appendConditionalInvalid(); } break; - case Location::Selfdestruct: + case FunctionType::Kind::Selfdestruct: arguments.front()->accept(*this); utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true); m_context << Instruction::SELFDESTRUCT; break; - case Location::Revert: + case FunctionType::Kind::Revert: // memory offset returned - zero length m_context << u256(0) << u256(0); m_context << Instruction::REVERT; break; - case Location::SHA3: + case FunctionType::Kind::SHA3: { TypePointers argumentTypes; for (auto const& arg: arguments) @@ -678,13 +677,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::SHA3; break; } - case Location::Log0: - case Location::Log1: - case Location::Log2: - case Location::Log3: - case Location::Log4: + case FunctionType::Kind::Log0: + case FunctionType::Kind::Log1: + case FunctionType::Kind::Log2: + case FunctionType::Kind::Log3: + case FunctionType::Kind::Log4: { - unsigned logNumber = int(function.location()) - int(Location::Log0); + unsigned logNumber = int(function.kind()) - int(FunctionType::Kind::Log0); for (unsigned arg = logNumber; arg > 0; --arg) { arguments[arg]->accept(*this); @@ -701,7 +700,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << logInstruction(logNumber); break; } - case Location::Event: + case FunctionType::Kind::Event: { _functionCall.expression().accept(*this); auto const& event = dynamic_cast<EventDefinition const&>(function.declaration()); @@ -755,50 +754,50 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << logInstruction(numIndexed); break; } - case Location::BlockHash: + case FunctionType::Kind::BlockHash: { arguments[0]->accept(*this); utils().convertType(*arguments[0]->annotation().type, *function.parameterTypes()[0], true); m_context << Instruction::BLOCKHASH; break; } - case Location::AddMod: - case Location::MulMod: + case FunctionType::Kind::AddMod: + case FunctionType::Kind::MulMod: { for (unsigned i = 0; i < 3; i ++) { arguments[2 - i]->accept(*this); utils().convertType(*arguments[2 - i]->annotation().type, IntegerType(256)); } - if (function.location() == Location::AddMod) + if (function.kind() == FunctionType::Kind::AddMod) m_context << Instruction::ADDMOD; else m_context << Instruction::MULMOD; break; } - case Location::ECRecover: - case Location::SHA256: - case Location::RIPEMD160: + case FunctionType::Kind::ECRecover: + case FunctionType::Kind::SHA256: + case FunctionType::Kind::RIPEMD160: { _functionCall.expression().accept(*this); - static const map<Location, u256> contractAddresses{{Location::ECRecover, 1}, - {Location::SHA256, 2}, - {Location::RIPEMD160, 3}}; - m_context << contractAddresses.find(function.location())->second; + static const map<FunctionType::Kind, u256> contractAddresses{{FunctionType::Kind::ECRecover, 1}, + {FunctionType::Kind::SHA256, 2}, + {FunctionType::Kind::RIPEMD160, 3}}; + m_context << contractAddresses.find(function.kind())->second; for (unsigned i = function.sizeOnStack(); i > 0; --i) m_context << swapInstruction(i); appendExternalFunctionCall(function, arguments); break; } - case Location::ByteArrayPush: - case Location::ArrayPush: + case FunctionType::Kind::ByteArrayPush: + case FunctionType::Kind::ArrayPush: { _functionCall.expression().accept(*this); solAssert(function.parameterTypes().size() == 1, ""); solAssert(!!function.parameterTypes()[0], ""); TypePointer paramType = function.parameterTypes()[0]; shared_ptr<ArrayType> arrayType = - function.location() == Location::ArrayPush ? + function.kind() == FunctionType::Kind::ArrayPush ? make_shared<ArrayType>(DataLocation::Storage, paramType) : make_shared<ArrayType>(DataLocation::Storage); // get the current length @@ -822,13 +821,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) utils().moveToStackTop(1 + type->sizeOnStack()); utils().moveToStackTop(1 + type->sizeOnStack()); // stack: newLength argValue storageSlot slotOffset - if (function.location() == Location::ArrayPush) + if (function.kind() == FunctionType::Kind::ArrayPush) StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true); else StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true); break; } - case Location::ObjectCreation: + case FunctionType::Kind::ObjectCreation: { // Will allocate at the end of memory (MSIZE) and not write at all unless the base // type is dynamically sized. @@ -878,15 +877,15 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::POP; break; } - case Location::Assert: - case Location::Require: + case FunctionType::Kind::Assert: + case FunctionType::Kind::Require: { arguments.front()->accept(*this); utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false); // jump if condition was met m_context << Instruction::ISZERO << Instruction::ISZERO; auto success = m_context.appendConditionalJump(); - if (function.location() == Location::Assert) + if (function.kind() == FunctionType::Kind::Assert) // condition was not met, flag an error m_context << Instruction::INVALID; else @@ -922,7 +921,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) *funType->selfType(), true ); - if (funType->location() == FunctionType::Location::Internal) + if (funType->kind() == FunctionType::Kind::Internal) { FunctionDefinition const& funDef = dynamic_cast<decltype(funDef)>(funType->declaration()); utils().pushCombinedFunctionEntryLabel(funDef); @@ -930,7 +929,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) } else { - solAssert(funType->location() == FunctionType::Location::DelegateCall, ""); + solAssert(funType->kind() == FunctionType::Kind::DelegateCall, ""); auto contract = dynamic_cast<ContractDefinition const*>(funType->declaration().scope()); solAssert(contract && contract->isLibrary(), ""); m_context.appendLibraryAddress(contract->fullyQualifiedName()); @@ -949,9 +948,9 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) solAssert(_memberAccess.annotation().type, "_memberAccess has no type"); if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get())) { - switch (funType->location()) + switch (funType->kind()) { - case FunctionType::Location::Internal: + case FunctionType::Kind::Internal: // We do not visit the expression here on purpose, because in the case of an // internal library function call, this would push the library address forcing // us to link against it although we actually do not need it. @@ -960,31 +959,31 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) else solAssert(false, "Function not found in member access"); break; - case FunctionType::Location::Event: + case FunctionType::Kind::Event: if (!dynamic_cast<EventDefinition const*>(_memberAccess.annotation().referencedDeclaration)) solAssert(false, "event not found"); // no-op, because the parent node will do the job break; - case FunctionType::Location::External: - case FunctionType::Location::Creation: - case FunctionType::Location::DelegateCall: - case FunctionType::Location::CallCode: - case FunctionType::Location::Send: - case FunctionType::Location::Bare: - case FunctionType::Location::BareCallCode: - case FunctionType::Location::BareDelegateCall: - case FunctionType::Location::Transfer: + case FunctionType::Kind::External: + case FunctionType::Kind::Creation: + case FunctionType::Kind::DelegateCall: + case FunctionType::Kind::CallCode: + case FunctionType::Kind::Send: + case FunctionType::Kind::Bare: + case FunctionType::Kind::BareCallCode: + case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::Transfer: _memberAccess.expression().accept(*this); m_context << funType->externalIdentifier(); break; - case FunctionType::Location::Log0: - case FunctionType::Location::Log1: - case FunctionType::Location::Log2: - case FunctionType::Location::Log3: - case FunctionType::Location::Log4: - case FunctionType::Location::ECRecover: - case FunctionType::Location::SHA256: - case FunctionType::Location::RIPEMD160: + case FunctionType::Kind::Log0: + case FunctionType::Kind::Log1: + case FunctionType::Kind::Log2: + case FunctionType::Kind::Log3: + case FunctionType::Kind::Log4: + case FunctionType::Kind::ECRecover: + case FunctionType::Kind::SHA256: + case FunctionType::Kind::RIPEMD160: default: solAssert(false, "unsupported member function"); } @@ -1372,7 +1371,7 @@ void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type { if (FunctionType const* funType = dynamic_cast<decltype(funType)>(&_type)) { - if (funType->location() == FunctionType::Location::Internal) + if (funType->kind() == FunctionType::Kind::Internal) { // We have to remove the upper bits (construction time value) because they might // be "unknown" in one of the operands and not in the other. @@ -1555,11 +1554,10 @@ void ExpressionCompiler::appendExternalFunctionCall( if (_functionType.bound()) utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack()); - using FunctionKind = FunctionType::Location; - FunctionKind funKind = _functionType.location(); - bool returnSuccessCondition = funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode; - bool isCallCode = funKind == FunctionKind::BareCallCode || funKind == FunctionKind::CallCode; - bool isDelegateCall = funKind == FunctionKind::BareDelegateCall || funKind == FunctionKind::DelegateCall; + auto funKind = _functionType.kind(); + bool returnSuccessCondition = funKind == FunctionType::Kind::Bare || funKind == FunctionType::Kind::BareCallCode; + bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode; + bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; unsigned retSize = 0; if (returnSuccessCondition) @@ -1576,7 +1574,7 @@ void ExpressionCompiler::appendExternalFunctionCall( TypePointers parameterTypes = _functionType.parameterTypes(); bool manualFunctionId = false; if ( - (funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode || funKind == FunctionKind::BareDelegateCall) && + (funKind == FunctionType::Kind::Bare || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall) && !_arguments.empty() ) { @@ -1611,7 +1609,7 @@ void ExpressionCompiler::appendExternalFunctionCall( argumentTypes.push_back(_arguments[i]->annotation().type); } - if (funKind == FunctionKind::ECRecover) + if (funKind == FunctionType::Kind::ECRecover) { // Clears 32 bytes of currently free memory and advances free memory pointer. // Output area will be "start of input area" - 32. @@ -1667,7 +1665,7 @@ void ExpressionCompiler::appendExternalFunctionCall( // put on stack: <size of output> <memory pos of output> <size of input> <memory pos of input> m_context << u256(retSize); utils().fetchFreeMemoryPointer(); // This is the start of input - if (funKind == FunctionKind::ECRecover) + if (funKind == FunctionType::Kind::ECRecover) { // In this case, output is 32 bytes before input and has already been cleared. m_context << u256(32) << Instruction::DUP2 << Instruction::SUB << Instruction::SWAP1; @@ -1693,7 +1691,7 @@ void ExpressionCompiler::appendExternalFunctionCall( bool existenceChecked = false; // Check the the target contract exists (has code) for non-low-level calls. - if (funKind == FunctionKind::External || funKind == FunctionKind::CallCode || funKind == FunctionKind::DelegateCall) + if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall) { m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO; m_context.appendConditionalInvalid(); @@ -1741,14 +1739,14 @@ void ExpressionCompiler::appendExternalFunctionCall( { // already there } - else if (funKind == FunctionKind::RIPEMD160) + else if (funKind == FunctionType::Kind::RIPEMD160) { // fix: built-in contract returns right-aligned data utils().fetchFreeMemoryPointer(); utils().loadFromMemoryDynamic(IntegerType(160), false, true, false); utils().convertType(IntegerType(160), FixedBytesType(20)); } - else if (funKind == FunctionKind::ECRecover) + else if (funKind == FunctionType::Kind::ECRecover) { // Output is 32 bytes before input / free mem pointer. // Failing ecrecover cannot be detected, so we clear output before the call. diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index b9e141d8..a74a3d74 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -199,7 +199,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const } else if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType)) { - if (fun->location() == FunctionType::Location::External) + if (fun->kind() == FunctionType::Kind::External) { CompilerUtils(m_context).splitExternalFunctionType(false); cleaned = true; @@ -256,7 +256,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType)) { solAssert(_sourceType == *m_dataType, "function item stored but target is not equal to source"); - if (fun->location() == FunctionType::Location::External) + if (fun->kind() == FunctionType::Kind::External) // Combine the two-item function type into a single stack slot. utils.combineExternalFunctionType(false); else diff --git a/libsolidity/formal/Why3Translator.cpp b/libsolidity/formal/Why3Translator.cpp index 2903a4e3..b6f17907 100644 --- a/libsolidity/formal/Why3Translator.cpp +++ b/libsolidity/formal/Why3Translator.cpp @@ -588,14 +588,14 @@ bool Why3Translator::visit(FunctionCall const& _node) return true; } FunctionType const& function = dynamic_cast<FunctionType const&>(*_node.expression().annotation().type); - switch (function.location()) + switch (function.kind()) { - case FunctionType::Location::AddMod: - case FunctionType::Location::MulMod: + case FunctionType::Kind::AddMod: + case FunctionType::Kind::MulMod: { //@todo require that third parameter is not zero add("(of_int (mod (Int.("); - add(function.location() == FunctionType::Location::AddMod ? "+" : "*"); + add(function.kind() == FunctionType::Kind::AddMod ? "+" : "*"); add(") (to_int "); _node.arguments().at(0)->accept(*this); add(") (to_int "); @@ -605,7 +605,7 @@ bool Why3Translator::visit(FunctionCall const& _node) add(")))"); return false; } - case FunctionType::Location::Internal: + case FunctionType::Kind::Internal: { if (!_node.names().empty()) { @@ -626,7 +626,7 @@ bool Why3Translator::visit(FunctionCall const& _node) add(")"); return false; } - case FunctionType::Location::Bare: + case FunctionType::Kind::Bare: { if (!_node.arguments().empty()) { @@ -654,7 +654,7 @@ bool Why3Translator::visit(FunctionCall const& _node) add(")"); return false; } - case FunctionType::Location::SetValue: + case FunctionType::Kind::SetValue: { add("let amount = "); solAssert(_node.arguments().size() == 1, ""); diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index a3ddb61d..dad05a78 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -21,6 +21,9 @@ #include <libsolidity/inlineasm/AsmAnalysis.h> #include <libsolidity/inlineasm/AsmData.h> +#include <libsolidity/inlineasm/AsmScopeFiller.h> +#include <libsolidity/inlineasm/AsmScope.h> +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> #include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/Utils.h> @@ -35,146 +38,363 @@ using namespace dev; using namespace dev::solidity; using namespace dev::solidity::assembly; - -bool Scope::registerLabel(string const& _name) +AsmAnalyzer::AsmAnalyzer( + AsmAnalysisInfo& _analysisInfo, + ErrorList& _errors, + ExternalIdentifierAccess::Resolver const& _resolver +): + m_resolver(_resolver), m_info(_analysisInfo), m_errors(_errors) { - if (exists(_name)) - return false; - identifiers[_name] = Label(); - return true; } -bool Scope::registerVariable(string const& _name) +bool AsmAnalyzer::analyze(Block const& _block) { - if (exists(_name)) + if (!(ScopeFiller(m_info.scopes, m_errors))(_block)) return false; - identifiers[_name] = Variable(); - return true; -} -bool Scope::registerFunction(string const& _name, size_t _arguments, size_t _returns) -{ - if (exists(_name)) - return false; - identifiers[_name] = Function(_arguments, _returns); - return true; + return (*this)(_block); } -Scope::Identifier* Scope::lookup(string const& _name) +bool AsmAnalyzer::operator()(Label const& _label) { - if (identifiers.count(_name)) - return &identifiers[_name]; - else if (superScope && !closedScope) - return superScope->lookup(_name); - else - return nullptr; -} - -bool Scope::exists(string const& _name) -{ - if (identifiers.count(_name)) - return true; - else if (superScope) - return superScope->exists(_name); - else - return false; + m_info.stackHeightInfo[&_label] = m_stackHeight; + return true; } -AsmAnalyzer::AsmAnalyzer(AsmAnalyzer::Scopes& _scopes, ErrorList& _errors): - m_scopes(_scopes), m_errors(_errors) +bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction) { - // Make the Solidity ErrorTag available to inline assembly - m_scopes[nullptr] = make_shared<Scope>(); - Scope::Label errorLabel; - errorLabel.id = Scope::Label::errorLabelId; - m_scopes[nullptr]->identifiers["invalidJumpLabel"] = errorLabel; - m_currentScope = m_scopes[nullptr].get(); + auto const& info = instructionInfo(_instruction.instruction); + m_stackHeight += info.ret - info.args; + m_info.stackHeightInfo[&_instruction] = m_stackHeight; + return true; } bool AsmAnalyzer::operator()(assembly::Literal const& _literal) { + ++m_stackHeight; if (!_literal.isNumber && _literal.value.size() > 32) { m_errors.push_back(make_shared<Error>( Error::Type::TypeError, - "String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)" + "String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)", + _literal.location )); return false; } + m_info.stackHeightInfo[&_literal] = m_stackHeight; return true; } +bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier) +{ + size_t numErrorsBefore = m_errors.size(); + bool success = true; + if (m_currentScope->lookup(_identifier.name, Scope::Visitor( + [&](Scope::Variable const& _var) + { + if (!_var.active) + { + m_errors.push_back(make_shared<Error>( + Error::Type::DeclarationError, + "Variable " + _identifier.name + " used before it was declared.", + _identifier.location + )); + success = false; + } + ++m_stackHeight; + }, + [&](Scope::Label const&) + { + ++m_stackHeight; + }, + [&](Scope::Function const&) + { + m_errors.push_back(make_shared<Error>( + Error::Type::TypeError, + "Function " + _identifier.name + " used without being called.", + _identifier.location + )); + success = false; + } + ))) + { + } + else + { + size_t stackSize(-1); + if (m_resolver) + stackSize = m_resolver(_identifier, IdentifierContext::RValue); + if (stackSize == size_t(-1)) + { + // Only add an error message if the callback did not do it. + if (numErrorsBefore == m_errors.size()) + m_errors.push_back(make_shared<Error>( + Error::Type::DeclarationError, + "Identifier not found.", + _identifier.location + )); + success = false; + } + m_stackHeight += stackSize == size_t(-1) ? 1 : stackSize; + } + m_info.stackHeightInfo[&_identifier] = m_stackHeight; + return success; +} + bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) { bool success = true; for (auto const& arg: _instr.arguments | boost::adaptors::reversed) + { + int const stackHeight = m_stackHeight; if (!boost::apply_visitor(*this, arg)) success = false; + if (!expectDeposit(1, stackHeight, locationOf(arg))) + success = false; + } + // Parser already checks that the number of arguments is correct. + solAssert(instructionInfo(_instr.instruction.instruction).args == int(_instr.arguments.size()), ""); if (!(*this)(_instr.instruction)) success = false; + m_info.stackHeightInfo[&_instr] = m_stackHeight; return success; } -bool AsmAnalyzer::operator()(Label const& _item) +bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment) { - if (!m_currentScope->registerLabel(_item.name)) - { - //@TODO secondary location - m_errors.push_back(make_shared<Error>( - Error::Type::DeclarationError, - "Label name " + _item.name + " already taken in this scope.", - _item.location - )); - return false; - } - return true; + bool success = checkAssignment(_assignment.variableName, size_t(-1)); + m_info.stackHeightInfo[&_assignment] = m_stackHeight; + return success; } + bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment) { - return boost::apply_visitor(*this, *_assignment.value); + int const stackHeight = m_stackHeight; + bool success = boost::apply_visitor(*this, *_assignment.value); + solAssert(m_stackHeight >= stackHeight, "Negative value size."); + if (!checkAssignment(_assignment.variableName, m_stackHeight - stackHeight)) + success = false; + m_info.stackHeightInfo[&_assignment] = m_stackHeight; + return success; } bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl) { + int const stackHeight = m_stackHeight; bool success = boost::apply_visitor(*this, *_varDecl.value); - if (!m_currentScope->registerVariable(_varDecl.name)) - { - //@TODO secondary location - m_errors.push_back(make_shared<Error>( - Error::Type::DeclarationError, - "Variable name " + _varDecl.name + " already taken in this scope.", - _varDecl.location - )); - success = false; - } + solAssert(m_stackHeight - stackHeight == 1, "Invalid value size."); + boost::get<Scope::Variable>(m_currentScope->identifiers.at(_varDecl.name)).active = true; + m_info.stackHeightInfo[&_varDecl] = m_stackHeight; return success; } -bool AsmAnalyzer::operator()(assembly::FunctionDefinition const&) +bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef) { - // TODO - we cannot throw an exception here because of some tests. - return true; + Scope& bodyScope = scope(&_funDef.body); + for (auto const& var: _funDef.arguments + _funDef.returns) + boost::get<Scope::Variable>(bodyScope.identifiers.at(var)).active = true; + + int const stackHeight = m_stackHeight; + m_stackHeight = _funDef.arguments.size() + _funDef.returns.size(); + m_virtualVariablesInNextBlock = m_stackHeight; + + bool success = (*this)(_funDef.body); + + m_stackHeight = stackHeight; + m_info.stackHeightInfo[&_funDef] = m_stackHeight; + return success; } -bool AsmAnalyzer::operator()(assembly::FunctionCall const&) +bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall) { - // TODO - we cannot throw an exception here because of some tests. - return true; + bool success = true; + size_t arguments = 0; + size_t returns = 0; + if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor( + [&](Scope::Variable const&) + { + m_errors.push_back(make_shared<Error>( + Error::Type::TypeError, + "Attempt to call variable instead of function.", + _funCall.functionName.location + )); + success = false; + }, + [&](Scope::Label const&) + { + m_errors.push_back(make_shared<Error>( + Error::Type::TypeError, + "Attempt to call label instead of function.", + _funCall.functionName.location + )); + success = false; + }, + [&](Scope::Function const& _fun) + { + arguments = _fun.arguments; + returns = _fun.returns; + } + ))) + { + m_errors.push_back(make_shared<Error>( + Error::Type::DeclarationError, + "Function not found.", + _funCall.functionName.location + )); + success = false; + } + if (success) + { + if (_funCall.arguments.size() != arguments) + { + m_errors.push_back(make_shared<Error>( + Error::Type::TypeError, + "Expected " + + boost::lexical_cast<string>(arguments) + + " arguments but got " + + boost::lexical_cast<string>(_funCall.arguments.size()) + + ".", + _funCall.functionName.location + )); + success = false; + } + } + for (auto const& arg: _funCall.arguments | boost::adaptors::reversed) + { + int const stackHeight = m_stackHeight; + if (!boost::apply_visitor(*this, arg)) + success = false; + if (!expectDeposit(1, stackHeight, locationOf(arg))) + success = false; + } + m_stackHeight += int(returns) - int(arguments); + m_info.stackHeightInfo[&_funCall] = m_stackHeight; + return success; } bool AsmAnalyzer::operator()(Block const& _block) { bool success = true; - auto scope = make_shared<Scope>(); - scope->superScope = m_currentScope; - m_scopes[&_block] = scope; - m_currentScope = scope.get(); + m_currentScope = &scope(&_block); + + int const initialStackHeight = m_stackHeight - m_virtualVariablesInNextBlock; + m_virtualVariablesInNextBlock = 0; for (auto const& s: _block.statements) if (!boost::apply_visitor(*this, s)) success = false; + for (auto const& identifier: scope(&_block).identifiers) + if (identifier.second.type() == typeid(Scope::Variable)) + --m_stackHeight; + + int const stackDiff = m_stackHeight - initialStackHeight; + if (stackDiff != 0) + { + m_errors.push_back(make_shared<Error>( + Error::Type::DeclarationError, + "Unbalanced stack at the end of a block: " + + ( + stackDiff > 0 ? + to_string(stackDiff) + string(" surplus item(s).") : + to_string(-stackDiff) + string(" missing item(s).") + ), + _block.location + )); + success = false; + } + m_currentScope = m_currentScope->superScope; + m_info.stackHeightInfo[&_block] = m_stackHeight; + return success; +} + +bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize) +{ + bool success = true; + size_t numErrorsBefore = m_errors.size(); + size_t variableSize(-1); + if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name)) + { + // Check that it is a variable + if (var->type() != typeid(Scope::Variable)) + { + m_errors.push_back(make_shared<Error>( + Error::Type::TypeError, + "Assignment requires variable.", + _variable.location + )); + success = false; + } + else if (!boost::get<Scope::Variable>(*var).active) + { + m_errors.push_back(make_shared<Error>( + Error::Type::DeclarationError, + "Variable " + _variable.name + " used before it was declared.", + _variable.location + )); + success = false; + } + variableSize = 1; + } + else if (m_resolver) + variableSize = m_resolver(_variable, IdentifierContext::LValue); + if (variableSize == size_t(-1)) + { + // Only add message if the callback did not. + if (numErrorsBefore == m_errors.size()) + m_errors.push_back(make_shared<Error>( + Error::Type::DeclarationError, + "Variable not found or variable not lvalue.", + _variable.location + )); + success = false; + } + if (_valueSize == size_t(-1)) + _valueSize = variableSize == size_t(-1) ? 1 : variableSize; + + m_stackHeight -= _valueSize; + + if (_valueSize != variableSize && variableSize != size_t(-1)) + { + m_errors.push_back(make_shared<Error>( + Error::Type::TypeError, + "Variable size (" + + to_string(variableSize) + + ") and value size (" + + to_string(_valueSize) + + ") do not match.", + _variable.location + )); + success = false; + } return success; } + +bool AsmAnalyzer::expectDeposit(int const _deposit, int const _oldHeight, SourceLocation const& _location) +{ + int stackDiff = m_stackHeight - _oldHeight; + if (stackDiff != _deposit) + { + m_errors.push_back(make_shared<Error>( + Error::Type::TypeError, + "Expected instruction(s) to deposit " + + boost::lexical_cast<string>(_deposit) + + " item(s) to the stack, but did deposit " + + boost::lexical_cast<string>(stackDiff) + + " item(s).", + _location + )); + return false; + } + else + return true; +} + +Scope& AsmAnalyzer::scope(Block const* _block) +{ + auto scopePtr = m_info.scopes.at(_block); + solAssert(scopePtr, "Scope requested but not present."); + return *scopePtr; +} diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h index 9726210d..426ee0d2 100644 --- a/libsolidity/inlineasm/AsmAnalysis.h +++ b/libsolidity/inlineasm/AsmAnalysis.h @@ -20,6 +20,8 @@ #pragma once +#include <libsolidity/inlineasm/AsmStack.h> + #include <libsolidity/interface/Exceptions.h> #include <boost/variant.hpp> @@ -46,101 +48,32 @@ struct Assignment; struct FunctionDefinition; struct FunctionCall; -template <class...> -struct GenericVisitor{}; - -template <class Visitable, class... Others> -struct GenericVisitor<Visitable, Others...>: public GenericVisitor<Others...> -{ - using GenericVisitor<Others...>::operator (); - explicit GenericVisitor( - std::function<void(Visitable&)> _visitor, - std::function<void(Others&)>... _otherVisitors - ): - GenericVisitor<Others...>(_otherVisitors...), - m_visitor(_visitor) - {} - - void operator()(Visitable& _v) const { m_visitor(_v); } - - std::function<void(Visitable&)> m_visitor; -}; -template <> -struct GenericVisitor<>: public boost::static_visitor<> { - void operator()() const {} -}; - - -struct Scope -{ - struct Variable - { - int stackHeight = 0; - bool active = false; - }; - - struct Label - { - size_t id = unassignedLabelId; - static const size_t errorLabelId = -1; - static const size_t unassignedLabelId = 0; - }; - - struct Function - { - Function(size_t _arguments, size_t _returns): arguments(_arguments), returns(_returns) {} - size_t arguments = 0; - size_t returns = 0; - }; - - using Identifier = boost::variant<Variable, Label, Function>; - using Visitor = GenericVisitor<Variable const, Label const, Function const>; - using NonconstVisitor = GenericVisitor<Variable, Label, Function>; - - bool registerVariable(std::string const& _name); - bool registerLabel(std::string const& _name); - bool registerFunction(std::string const& _name, size_t _arguments, size_t _returns); - - /// Looks up the identifier in this or super scopes (stops and function and assembly boundaries) - /// and returns a valid pointer if found or a nullptr if not found. - /// The pointer will be invalidated if the scope is modified. - Identifier* lookup(std::string const& _name); - /// Looks up the identifier in this and super scopes (stops and function and assembly boundaries) - /// and calls the visitor, returns false if not found. - template <class V> - bool lookup(std::string const& _name, V const& _visitor) - { - if (Identifier* id = lookup(_name)) - { - boost::apply_visitor(_visitor, *id); - return true; - } - else - return false; - } - /// @returns true if the name exists in this scope or in super scopes (also searches - /// across function and assembly boundaries). - bool exists(std::string const& _name); - Scope* superScope = nullptr; - /// If true, identifiers from the super scope are not visible here, but they are still - /// taken into account to prevent shadowing. - bool closedScope = false; - std::map<std::string, Identifier> identifiers; -}; +struct Scope; +struct AsmAnalysisInfo; +/** + * Performs the full analysis stage, calls the ScopeFiller internally, then resolves + * references and performs other checks. + * If all these checks pass, code generation should not throw errors. + */ class AsmAnalyzer: public boost::static_visitor<bool> { public: - using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>; - AsmAnalyzer(Scopes& _scopes, ErrorList& _errors); + AsmAnalyzer( + AsmAnalysisInfo& _analysisInfo, + ErrorList& _errors, + ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver() + ); + + bool analyze(assembly::Block const& _block); - bool operator()(assembly::Instruction const&) { return true; } + bool operator()(assembly::Instruction const&); bool operator()(assembly::Literal const& _literal); - bool operator()(assembly::Identifier const&) { return true; } + bool operator()(assembly::Identifier const&); bool operator()(assembly::FunctionalInstruction const& _functionalInstruction); bool operator()(assembly::Label const& _label); - bool operator()(assembly::Assignment const&) { return true; } + bool operator()(assembly::Assignment const&); bool operator()(assembly::FunctionalAssignment const& _functionalAssignment); bool operator()(assembly::VariableDeclaration const& _variableDeclaration); bool operator()(assembly::FunctionDefinition const& _functionDefinition); @@ -148,8 +81,20 @@ public: bool operator()(assembly::Block const& _block); private: + /// Verifies that a variable to be assigned to exists and has the same size + /// as the value, @a _valueSize, unless that is equal to -1. + bool checkAssignment(assembly::Identifier const& _assignment, size_t _valueSize = size_t(-1)); + bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location); + Scope& scope(assembly::Block const* _block); + + /// This is used when we enter the body of a function definition. There, the parameters + /// and return parameters appear as variables which are already on the stack before + /// we enter the block. + int m_virtualVariablesInNextBlock = 0; + int m_stackHeight = 0; + ExternalIdentifierAccess::Resolver const& m_resolver; Scope* m_currentScope = nullptr; - Scopes& m_scopes; + AsmAnalysisInfo& m_info; ErrorList& m_errors; }; diff --git a/libsolidity/inlineasm/AsmAnalysisInfo.cpp b/libsolidity/inlineasm/AsmAnalysisInfo.cpp new file mode 100644 index 00000000..22318b12 --- /dev/null +++ b/libsolidity/inlineasm/AsmAnalysisInfo.cpp @@ -0,0 +1,26 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Information generated during analyzer part of inline assembly. + */ + +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> + +#include <libsolidity/inlineasm/AsmScope.h> + +#include <ostream> + diff --git a/libsolidity/inlineasm/AsmAnalysisInfo.h b/libsolidity/inlineasm/AsmAnalysisInfo.h new file mode 100644 index 00000000..e21eb2c5 --- /dev/null +++ b/libsolidity/inlineasm/AsmAnalysisInfo.h @@ -0,0 +1,61 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Information generated during analyzer part of inline assembly. + */ + +#pragma once + +#include <boost/variant.hpp> + +#include <map> +#include <memory> + +namespace dev +{ +namespace solidity +{ +namespace assembly +{ + +struct Literal; +struct Block; +struct Label; +struct FunctionalInstruction; +struct FunctionalAssignment; +struct VariableDeclaration; +struct Instruction; +struct Identifier; +struct Assignment; +struct FunctionDefinition; +struct FunctionCall; + +struct Scope; + +using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Block>; + +struct AsmAnalysisInfo +{ + using StackHeightInfo = std::map<void const*, int>; + using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>; + Scopes scopes; + StackHeightInfo stackHeightInfo; +}; + +} +} +} diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index 78a9ee27..9ef3e6e7 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -24,7 +24,9 @@ #include <libsolidity/inlineasm/AsmParser.h> #include <libsolidity/inlineasm/AsmData.h> +#include <libsolidity/inlineasm/AsmScope.h> #include <libsolidity/inlineasm/AsmAnalysis.h> +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> #include <libevmasm/Assembly.h> #include <libevmasm/SourceLocation.h> @@ -46,13 +48,8 @@ using namespace dev::solidity::assembly; struct GeneratorState { - GeneratorState(ErrorList& _errors, eth::Assembly& _assembly): - errors(_errors), assembly(_assembly) {} - - void addError(Error::Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation()) - { - errors.push_back(make_shared<Error>(_type, _description, _location)); - } + GeneratorState(ErrorList& _errors, AsmAnalysisInfo& _analysisInfo, eth::Assembly& _assembly): + errors(_errors), info(_analysisInfo), assembly(_assembly) {} size_t newLabelId() { @@ -66,8 +63,8 @@ struct GeneratorState return size_t(id); } - std::map<assembly::Block const*, shared_ptr<Scope>> scopes; ErrorList& errors; + AsmAnalysisInfo info; eth::Assembly& assembly; }; @@ -80,13 +77,24 @@ public: explicit CodeTransform( GeneratorState& _state, assembly::Block const& _block, - assembly::CodeGenerator::IdentifierAccess const& _identifierAccess = assembly::CodeGenerator::IdentifierAccess() + assembly::ExternalIdentifierAccess const& _identifierAccess = assembly::ExternalIdentifierAccess() + ): CodeTransform(_state, _block, _identifierAccess, _state.assembly.deposit()) + { + } + +private: + CodeTransform( + GeneratorState& _state, + assembly::Block const& _block, + assembly::ExternalIdentifierAccess const& _identifierAccess, + int _initialDeposit ): m_state(_state), - m_scope(*m_state.scopes.at(&_block)), - m_initialDeposit(m_state.assembly.deposit()), - m_identifierAccess(_identifierAccess) + m_scope(*m_state.info.scopes.at(&_block)), + m_identifierAccess(_identifierAccess), + m_initialDeposit(_initialDeposit) { + int blockStartDeposit = m_state.assembly.deposit(); std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this)); m_state.assembly.setSourceLocation(_block.location); @@ -96,31 +104,16 @@ public: if (identifier.second.type() == typeid(Scope::Variable)) m_state.assembly.append(solidity::Instruction::POP); - int deposit = m_state.assembly.deposit() - m_initialDeposit; - - // issue warnings for stack height discrepancies - if (deposit < 0) - { - m_state.addError( - Error::Type::Warning, - "Inline assembly block is not balanced. It takes " + toString(-deposit) + " item(s) from the stack.", - _block.location - ); - } - else if (deposit > 0) - { - m_state.addError( - Error::Type::Warning, - "Inline assembly block is not balanced. It leaves " + toString(deposit) + " item(s) on the stack.", - _block.location - ); - } + int deposit = m_state.assembly.deposit() - blockStartDeposit; + solAssert(deposit == 0, "Invalid stack height at end of block."); } +public: void operator()(assembly::Instruction const& _instruction) { m_state.assembly.setSourceLocation(_instruction.location); m_state.assembly.append(_instruction.instruction); + checkStackHeight(&_instruction); } void operator()(assembly::Literal const& _literal) { @@ -130,8 +123,9 @@ public: else { solAssert(_literal.value.size() <= 32, ""); - m_state.assembly.append(_literal.value); + m_state.assembly.append(u256(h256(_literal.value, h256::FromBinary, h256::AlignLeft))); } + checkStackHeight(&_literal); } void operator()(assembly::Identifier const& _identifier) { @@ -153,20 +147,18 @@ public: }, [=](Scope::Function&) { - solAssert(false, "Not yet implemented"); + solAssert(false, "Function not removed during desugaring."); } ))) { + return; } - else if (!m_identifierAccess || !m_identifierAccess(_identifier, m_state.assembly, CodeGenerator::IdentifierContext::RValue)) - { - m_state.addError( - Error::Type::DeclarationError, - "Identifier not found or not unique", - _identifier.location - ); - m_state.assembly.append(u256(0)); - } + solAssert( + m_identifierAccess.generateCode, + "Identifier not found and no external access available." + ); + m_identifierAccess.generateCode(_identifier, IdentifierContext::RValue, m_state.assembly); + checkStackHeight(&_identifier); } void operator()(FunctionalInstruction const& _instr) { @@ -174,9 +166,10 @@ public: { int height = m_state.assembly.deposit(); boost::apply_visitor(*this, *it); - expectDeposit(1, height, locationOf(*it)); + expectDeposit(1, height); } (*this)(_instr.instruction); + checkStackHeight(&_instr); } void operator()(assembly::FunctionCall const&) { @@ -186,36 +179,39 @@ public: { m_state.assembly.setSourceLocation(_label.location); solAssert(m_scope.identifiers.count(_label.name), ""); - Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers[_label.name]); + Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers.at(_label.name)); assignLabelIdIfUnset(label); m_state.assembly.append(eth::AssemblyItem(eth::Tag, label.id)); + checkStackHeight(&_label); } void operator()(assembly::Assignment const& _assignment) { m_state.assembly.setSourceLocation(_assignment.location); generateAssignment(_assignment.variableName, _assignment.location); + checkStackHeight(&_assignment); } void operator()(FunctionalAssignment const& _assignment) { int height = m_state.assembly.deposit(); boost::apply_visitor(*this, *_assignment.value); - expectDeposit(1, height, locationOf(*_assignment.value)); + expectDeposit(1, height); m_state.assembly.setSourceLocation(_assignment.location); generateAssignment(_assignment.variableName, _assignment.location); + checkStackHeight(&_assignment); } void operator()(assembly::VariableDeclaration const& _varDecl) { int height = m_state.assembly.deposit(); boost::apply_visitor(*this, *_varDecl.value); - expectDeposit(1, height, locationOf(*_varDecl.value)); - solAssert(m_scope.identifiers.count(_varDecl.name), ""); - auto& var = boost::get<Scope::Variable>(m_scope.identifiers[_varDecl.name]); + expectDeposit(1, height); + auto& var = boost::get<Scope::Variable>(m_scope.identifiers.at(_varDecl.name)); var.stackHeight = height; var.active = true; } void operator()(assembly::Block const& _block) { - CodeTransform(m_state, _block, m_identifierAccess); + CodeTransform(m_state, _block, m_identifierAccess, m_initialDeposit); + checkStackHeight(&_block); } void operator()(assembly::FunctionDefinition const&) { @@ -225,35 +221,22 @@ public: private: void generateAssignment(assembly::Identifier const& _variableName, SourceLocation const& _location) { - if (m_scope.lookup(_variableName.name, Scope::Visitor( - [=](Scope::Variable const& _var) - { - if (int heightDiff = variableHeightDiff(_var, _location, true)) - m_state.assembly.append(solidity::swapInstruction(heightDiff - 1)); - m_state.assembly.append(solidity::Instruction::POP); - }, - [=](Scope::Label const&) - { - m_state.addError( - Error::Type::DeclarationError, - "Label \"" + string(_variableName.name) + "\" used as variable." - ); - }, - [=](Scope::Function const&) - { - m_state.addError( - Error::Type::DeclarationError, - "Function \"" + string(_variableName.name) + "\" used as variable." - ); - } - ))) + auto var = m_scope.lookup(_variableName.name); + if (var) { + Scope::Variable const& _var = boost::get<Scope::Variable>(*var); + if (int heightDiff = variableHeightDiff(_var, _location, true)) + m_state.assembly.append(solidity::swapInstruction(heightDiff - 1)); + m_state.assembly.append(solidity::Instruction::POP); } - else if (!m_identifierAccess || !m_identifierAccess(_variableName, m_state.assembly, CodeGenerator::IdentifierContext::LValue)) - m_state.addError( - Error::Type::DeclarationError, - "Identifier \"" + string(_variableName.name) + "\" not found, not unique or not lvalue." + else + { + solAssert( + m_identifierAccess.generateCode, + "Identifier not found and no external access available." ); + m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_state.assembly); + } } /// Determines the stack height difference to the given variables. Automatically generates @@ -261,36 +244,33 @@ private: /// errors and the (positive) stack height difference otherwise. int variableHeightDiff(Scope::Variable const& _var, SourceLocation const& _location, bool _forSwap) { - if (!_var.active) - { - m_state.addError( Error::Type::TypeError, "Variable used before it was declared", _location); - return 0; - } int heightDiff = m_state.assembly.deposit() - _var.stackHeight; if (heightDiff <= (_forSwap ? 1 : 0) || heightDiff > (_forSwap ? 17 : 16)) { - m_state.addError( + //@TODO move this to analysis phase. + m_state.errors.push_back(make_shared<Error>( Error::Type::TypeError, "Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")", _location - ); + )); return 0; } else return heightDiff; } - void expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location) + void expectDeposit(int _deposit, int _oldHeight) { - if (m_state.assembly.deposit() != _oldHeight + 1) - m_state.addError(Error::Type::TypeError, - "Expected instruction(s) to deposit " + - boost::lexical_cast<string>(_deposit) + - " item(s) to the stack, but did deposit " + - boost::lexical_cast<string>(m_state.assembly.deposit() - _oldHeight) + - " item(s).", - _location - ); + solAssert(m_state.assembly.deposit() == _oldHeight + _deposit, "Invalid stack deposit."); + } + + void checkStackHeight(void const* _astElement) + { + solAssert(m_state.info.stackHeightInfo.count(_astElement), "Stack height for AST element not found."); + solAssert( + m_state.info.stackHeightInfo.at(_astElement) == m_state.assembly.deposit() - m_initialDeposit, + "Stack height mismatch between analysis and code generation phase." + ); } /// Assigns the label's id to a value taken from eth::Assembly if it has not yet been set. @@ -305,35 +285,29 @@ private: GeneratorState& m_state; Scope& m_scope; + ExternalIdentifierAccess m_identifierAccess; int const m_initialDeposit; - assembly::CodeGenerator::IdentifierAccess m_identifierAccess; }; -bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) -{ - size_t initialErrorLen = m_errors.size(); - eth::Assembly assembly; - GeneratorState state(m_errors, assembly); - if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData)) - return false; - CodeTransform(state, m_parsedData, _identifierAccess); - return m_errors.size() == initialErrorLen; -} - -eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) +eth::Assembly assembly::CodeGenerator::assemble( + Block const& _parsedData, + AsmAnalysisInfo& _analysisInfo, + ExternalIdentifierAccess const& _identifierAccess +) { eth::Assembly assembly; - GeneratorState state(m_errors, assembly); - if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData)) - solAssert(false, "Assembly error"); - CodeTransform(state, m_parsedData, _identifierAccess); + GeneratorState state(m_errors, _analysisInfo, assembly); + CodeTransform(state, _parsedData, _identifierAccess); return assembly; } -void assembly::CodeGenerator::assemble(eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) +void assembly::CodeGenerator::assemble( + Block const& _parsedData, + AsmAnalysisInfo& _analysisInfo, + eth::Assembly& _assembly, + ExternalIdentifierAccess const& _identifierAccess +) { - GeneratorState state(m_errors, _assembly); - if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData)) - solAssert(false, "Assembly error"); - CodeTransform(state, m_parsedData, _identifierAccess); + GeneratorState state(m_errors, _analysisInfo, _assembly); + CodeTransform(state, _parsedData, _identifierAccess); } diff --git a/libsolidity/inlineasm/AsmCodeGen.h b/libsolidity/inlineasm/AsmCodeGen.h index bd71812e..e830e047 100644 --- a/libsolidity/inlineasm/AsmCodeGen.h +++ b/libsolidity/inlineasm/AsmCodeGen.h @@ -22,9 +22,11 @@ #pragma once -#include <functional> +#include <libsolidity/inlineasm/AsmAnalysis.h> #include <libsolidity/interface/Exceptions.h> +#include <functional> + namespace dev { namespace eth @@ -36,30 +38,27 @@ namespace solidity namespace assembly { struct Block; -struct Identifier; class CodeGenerator { public: - enum class IdentifierContext { LValue, RValue }; - /// Function type that is called for external identifiers. Such a function should search for - /// the identifier and append appropriate assembly items to the assembly. If in lvalue context, - /// the value to assign is assumed to be on the stack and an assignment is to be performed. - /// If in rvalue context, the function is assumed to append instructions to - /// push the value of the identifier onto the stack. On error, the function should return false. - using IdentifierAccess = std::function<bool(assembly::Identifier const&, eth::Assembly&, IdentifierContext)>; - CodeGenerator(Block const& _parsedData, ErrorList& _errors): - m_parsedData(_parsedData), m_errors(_errors) {} - /// Performs type checks and @returns false on error. - /// Actually runs the full code generation but discards the result. - bool typeCheck(IdentifierAccess const& _identifierAccess = IdentifierAccess()); + CodeGenerator(ErrorList& _errors): + m_errors(_errors) {} /// Performs code generation and @returns the result. - eth::Assembly assemble(IdentifierAccess const& _identifierAccess = IdentifierAccess()); + eth::Assembly assemble( + Block const& _parsedData, + AsmAnalysisInfo& _analysisInfo, + ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess() + ); /// Performs code generation and appends generated to to _assembly. - void assemble(eth::Assembly& _assembly, IdentifierAccess const& _identifierAccess = IdentifierAccess()); + void assemble( + Block const& _parsedData, + AsmAnalysisInfo& _analysisInfo, + eth::Assembly& _assembly, + ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess() + ); private: - Block const& m_parsedData; ErrorList& m_errors; }; diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 0fc0a34f..d7f78958 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -24,6 +24,7 @@ #include <ctype.h> #include <algorithm> #include <libsolidity/parsing/Scanner.h> +#include <libsolidity/interface/Exceptions.h> using namespace std; using namespace dev; @@ -68,12 +69,14 @@ assembly::Statement Parser::parseStatement() return parseBlock(); case Token::Assign: { + if (m_julia) + break; assembly::Assignment assignment = createWithLocation<assembly::Assignment>(); m_scanner->next(); expectToken(Token::Colon); assignment.variableName.location = location(); assignment.variableName.name = m_scanner->currentLiteral(); - if (instructions().count(assignment.variableName.name)) + if (!m_julia && instructions().count(assignment.variableName.name)) fatalParserError("Identifier expected, got instruction name."); assignment.location.end = endPosition(); expectToken(Token::Identifier); @@ -105,7 +108,7 @@ assembly::Statement Parser::parseStatement() { // functional assignment FunctionalAssignment funAss = createWithLocation<FunctionalAssignment>(identifier.location); - if (instructions().count(identifier.name)) + if (!m_julia && instructions().count(identifier.name)) fatalParserError("Cannot use instruction names for identifier names."); m_scanner->next(); funAss.variableName = identifier; @@ -180,7 +183,7 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) else literal = m_scanner->currentLiteral(); // first search the set of instructions. - if (instructions().count(literal)) + if (!m_julia && instructions().count(literal)) { dev::solidity::Instruction const& instr = instructions().at(literal); if (_onlySinglePusher) @@ -242,15 +245,13 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition() { expectToken(Token::Sub); expectToken(Token::GreaterThan); - expectToken(Token::LParen); while (true) { funDef.returns.push_back(expectAsmIdentifier()); - if (m_scanner->currentToken() == Token::RParen) + if (m_scanner->currentToken() == Token::LBrace) break; expectToken(Token::Comma); } - expectToken(Token::RParen); } funDef.body = parseBlock(); funDef.location.end = funDef.body.location.end; @@ -261,6 +262,7 @@ assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _in { if (_instruction.type() == typeid(Instruction)) { + solAssert(!m_julia, "Instructions are invalid in JULIA"); FunctionalInstruction ret; ret.instruction = std::move(boost::get<Instruction>(_instruction)); ret.location = ret.instruction.location; @@ -323,7 +325,7 @@ assembly::Statement Parser::parseFunctionalInstruction(assembly::Statement&& _in string Parser::expectAsmIdentifier() { string name = m_scanner->currentLiteral(); - if (instructions().count(name)) + if (!m_julia && instructions().count(name)) fatalParserError("Cannot use instruction names for identifier names."); expectToken(Token::Identifier); return name; diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index 4b4a24ae..c55fd2ac 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -37,7 +37,7 @@ namespace assembly class Parser: public ParserBase { public: - Parser(ErrorList& _errors): ParserBase(_errors) {} + explicit Parser(ErrorList& _errors, bool _julia = false): ParserBase(_errors), m_julia(_julia) {} /// Parses an inline assembly block starting with `{` and ending with `}`. /// @returns an empty shared pointer on error. @@ -70,6 +70,9 @@ protected: FunctionDefinition parseFunctionDefinition(); Statement parseFunctionalInstruction(Statement&& _instruction); std::string expectAsmIdentifier(); + +private: + bool m_julia = false; }; } diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index a70b0b78..252e91f9 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -116,7 +116,7 @@ string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefin { string out = "function " + _functionDefinition.name + "(" + boost::algorithm::join(_functionDefinition.arguments, ", ") + ")"; if (!_functionDefinition.returns.empty()) - out += " -> (" + boost::algorithm::join(_functionDefinition.returns, ", ") + ")"; + out += " -> " + boost::algorithm::join(_functionDefinition.returns, ", "); return out + "\n" + (*this)(_functionDefinition.body); } diff --git a/libsolidity/inlineasm/AsmScope.cpp b/libsolidity/inlineasm/AsmScope.cpp new file mode 100644 index 00000000..609dca16 --- /dev/null +++ b/libsolidity/inlineasm/AsmScope.cpp @@ -0,0 +1,79 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Scopes for identifiers. + */ + +#include <libsolidity/inlineasm/AsmScope.h> + +using namespace std; +using namespace dev::solidity::assembly; + + +bool Scope::registerLabel(string const& _name) +{ + if (exists(_name)) + return false; + identifiers[_name] = Label(); + return true; +} + +bool Scope::registerVariable(string const& _name) +{ + if (exists(_name)) + return false; + identifiers[_name] = Variable(); + return true; +} + +bool Scope::registerFunction(string const& _name, size_t _arguments, size_t _returns) +{ + if (exists(_name)) + return false; + identifiers[_name] = Function(_arguments, _returns); + return true; +} + +Scope::Identifier* Scope::lookup(string const& _name) +{ + bool crossedFunctionBoundary = false; + for (Scope* s = this; s; s = s->superScope) + { + auto id = s->identifiers.find(_name); + if (id != s->identifiers.end()) + { + if (crossedFunctionBoundary && id->second.type() == typeid(Scope::Variable)) + return nullptr; + else + return &id->second; + } + + if (s->functionScope) + crossedFunctionBoundary = true; + } + return nullptr; +} + +bool Scope::exists(string const& _name) +{ + if (identifiers.count(_name)) + return true; + else if (superScope) + return superScope->exists(_name); + else + return false; +} diff --git a/libsolidity/inlineasm/AsmScope.h b/libsolidity/inlineasm/AsmScope.h new file mode 100644 index 00000000..37e0f0b8 --- /dev/null +++ b/libsolidity/inlineasm/AsmScope.h @@ -0,0 +1,128 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Scopes for identifiers. + */ + +#pragma once + +#include <libsolidity/interface/Exceptions.h> + +#include <boost/variant.hpp> + +#include <functional> +#include <memory> + +namespace dev +{ +namespace solidity +{ +namespace assembly +{ + +template <class...> +struct GenericVisitor{}; + +template <class Visitable, class... Others> +struct GenericVisitor<Visitable, Others...>: public GenericVisitor<Others...> +{ + using GenericVisitor<Others...>::operator (); + explicit GenericVisitor( + std::function<void(Visitable&)> _visitor, + std::function<void(Others&)>... _otherVisitors + ): + GenericVisitor<Others...>(_otherVisitors...), + m_visitor(_visitor) + {} + + void operator()(Visitable& _v) const { m_visitor(_v); } + + std::function<void(Visitable&)> m_visitor; +}; +template <> +struct GenericVisitor<>: public boost::static_visitor<> { + void operator()() const {} +}; + + +struct Scope +{ + struct Variable + { + /// Used during code generation to store the stack height. @todo move there. + int stackHeight = 0; + /// Used during analysis to check whether we already passed the declaration inside the block. + /// @todo move there. + bool active = false; + }; + + struct Label + { + size_t id = unassignedLabelId; + static const size_t errorLabelId = -1; + static const size_t unassignedLabelId = 0; + }; + + struct Function + { + Function(size_t _arguments, size_t _returns): arguments(_arguments), returns(_returns) {} + size_t arguments = 0; + size_t returns = 0; + }; + + using Identifier = boost::variant<Variable, Label, Function>; + using Visitor = GenericVisitor<Variable const, Label const, Function const>; + using NonconstVisitor = GenericVisitor<Variable, Label, Function>; + + bool registerVariable(std::string const& _name); + bool registerLabel(std::string const& _name); + bool registerFunction(std::string const& _name, size_t _arguments, size_t _returns); + + /// Looks up the identifier in this or super scopes and returns a valid pointer if found + /// or a nullptr if not found. Variable lookups up across function boundaries will fail, as + /// will any lookups across assembly boundaries. + /// The pointer will be invalidated if the scope is modified. + /// @param _crossedFunction if true, we already crossed a function boundary during recursive lookup + Identifier* lookup(std::string const& _name); + /// Looks up the identifier in this and super scopes (will not find variables across function + /// boundaries and generally stops at assembly boundaries) and calls the visitor, returns + /// false if not found. + template <class V> + bool lookup(std::string const& _name, V const& _visitor) + { + if (Identifier* id = lookup(_name)) + { + boost::apply_visitor(_visitor, *id); + return true; + } + else + return false; + } + /// @returns true if the name exists in this scope or in super scopes (also searches + /// across function and assembly boundaries). + bool exists(std::string const& _name); + + Scope* superScope = nullptr; + /// If true, variables from the super scope are not visible here (other identifiers are), + /// but they are still taken into account to prevent shadowing. + bool functionScope = false; + std::map<std::string, Identifier> identifiers; +}; + +} +} +} diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp new file mode 100644 index 00000000..de6fbdaa --- /dev/null +++ b/libsolidity/inlineasm/AsmScopeFiller.cpp @@ -0,0 +1,130 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Module responsible for registering identifiers inside their scopes. + */ + +#include <libsolidity/inlineasm/AsmScopeFiller.h> + +#include <libsolidity/inlineasm/AsmData.h> +#include <libsolidity/inlineasm/AsmScope.h> + +#include <libsolidity/interface/Exceptions.h> +#include <libsolidity/interface/Utils.h> + +#include <boost/range/adaptor/reversed.hpp> + +#include <memory> +#include <functional> + +using namespace std; +using namespace dev; +using namespace dev::solidity; +using namespace dev::solidity::assembly; + +ScopeFiller::ScopeFiller(ScopeFiller::Scopes& _scopes, ErrorList& _errors): + m_scopes(_scopes), m_errors(_errors) +{ + // Make the Solidity ErrorTag available to inline assembly + Scope::Label errorLabel; + errorLabel.id = Scope::Label::errorLabelId; + scope(nullptr).identifiers["invalidJumpLabel"] = errorLabel; + m_currentScope = &scope(nullptr); +} + +bool ScopeFiller::operator()(Label const& _item) +{ + if (!m_currentScope->registerLabel(_item.name)) + { + //@TODO secondary location + m_errors.push_back(make_shared<Error>( + Error::Type::DeclarationError, + "Label name " + _item.name + " already taken in this scope.", + _item.location + )); + return false; + } + return true; +} + +bool ScopeFiller::operator()(assembly::VariableDeclaration const& _varDecl) +{ + return registerVariable(_varDecl.name, _varDecl.location, *m_currentScope); +} + +bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef) +{ + bool success = true; + if (!m_currentScope->registerFunction(_funDef.name, _funDef.arguments.size(), _funDef.returns.size())) + { + //@TODO secondary location + m_errors.push_back(make_shared<Error>( + Error::Type::DeclarationError, + "Function name " + _funDef.name + " already taken in this scope.", + _funDef.location + )); + success = false; + } + Scope& body = scope(&_funDef.body); + body.superScope = m_currentScope; + body.functionScope = true; + for (auto const& var: _funDef.arguments + _funDef.returns) + if (!registerVariable(var, _funDef.location, body)) + success = false; + + if (!(*this)(_funDef.body)) + success = false; + + return success; +} + +bool ScopeFiller::operator()(Block const& _block) +{ + bool success = true; + scope(&_block).superScope = m_currentScope; + m_currentScope = &scope(&_block); + + for (auto const& s: _block.statements) + if (!boost::apply_visitor(*this, s)) + success = false; + + m_currentScope = m_currentScope->superScope; + return success; +} + +bool ScopeFiller::registerVariable(string const& _name, SourceLocation const& _location, Scope& _scope) +{ + if (!_scope.registerVariable(_name)) + { + //@TODO secondary location + m_errors.push_back(make_shared<Error>( + Error::Type::DeclarationError, + "Variable name " + _name + " already taken in this scope.", + _location + )); + return false; + } + return true; +} + +Scope& ScopeFiller::scope(Block const* _block) +{ + auto& scope = m_scopes[_block]; + if (!scope) + scope = make_shared<Scope>(); + return *scope; +} diff --git a/libsolidity/inlineasm/AsmScopeFiller.h b/libsolidity/inlineasm/AsmScopeFiller.h new file mode 100644 index 00000000..bb62948b --- /dev/null +++ b/libsolidity/inlineasm/AsmScopeFiller.h @@ -0,0 +1,89 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Module responsible for registering identifiers inside their scopes. + */ + +#pragma once + +#include <libsolidity/interface/Exceptions.h> + +#include <boost/variant.hpp> + +#include <functional> +#include <memory> + +namespace dev +{ +namespace solidity +{ +namespace assembly +{ + +struct Literal; +struct Block; +struct Label; +struct FunctionalInstruction; +struct FunctionalAssignment; +struct VariableDeclaration; +struct Instruction; +struct Identifier; +struct Assignment; +struct FunctionDefinition; +struct FunctionCall; + +struct Scope; + +/** + * Fills scopes with identifiers and checks for name clashes. + * Does not resolve references. + */ +class ScopeFiller: public boost::static_visitor<bool> +{ +public: + using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>; + ScopeFiller(Scopes& _scopes, ErrorList& _errors); + + bool operator()(assembly::Instruction const&) { return true; } + bool operator()(assembly::Literal const&) { return true; } + bool operator()(assembly::Identifier const&) { return true; } + bool operator()(assembly::FunctionalInstruction const&) { return true; } + bool operator()(assembly::Label const& _label); + bool operator()(assembly::Assignment const&) { return true; } + bool operator()(assembly::FunctionalAssignment const&) { return true; } + bool operator()(assembly::VariableDeclaration const& _variableDeclaration); + bool operator()(assembly::FunctionDefinition const& _functionDefinition); + bool operator()(assembly::FunctionCall const&) { return true; } + bool operator()(assembly::Block const& _block); + +private: + bool registerVariable( + std::string const& _name, + SourceLocation const& _location, + Scope& _scope + ); + + Scope& scope(assembly::Block const* _block); + + Scope* m_currentScope = nullptr; + Scopes& m_scopes; + ErrorList& m_errors; +}; + +} +} +} diff --git a/libsolidity/inlineasm/AsmStack.cpp b/libsolidity/inlineasm/AsmStack.cpp index 266136a1..c2a7d8ea 100644 --- a/libsolidity/inlineasm/AsmStack.cpp +++ b/libsolidity/inlineasm/AsmStack.cpp @@ -26,6 +26,7 @@ #include <libsolidity/inlineasm/AsmCodeGen.h> #include <libsolidity/inlineasm/AsmPrinter.h> #include <libsolidity/inlineasm/AsmAnalysis.h> +#include <libsolidity/inlineasm/AsmAnalysisInfo.h> #include <libsolidity/parsing/Scanner.h> @@ -39,7 +40,10 @@ using namespace dev; using namespace dev::solidity; using namespace dev::solidity::assembly; -bool InlineAssemblyStack::parse(shared_ptr<Scanner> const& _scanner) +bool InlineAssemblyStack::parse( + shared_ptr<Scanner> const& _scanner, + ExternalIdentifierAccess::Resolver const& _resolver +) { m_parserResult = make_shared<Block>(); Parser parser(m_errors); @@ -48,8 +52,8 @@ bool InlineAssemblyStack::parse(shared_ptr<Scanner> const& _scanner) return false; *m_parserResult = std::move(*result); - AsmAnalyzer::Scopes scopes; - return (AsmAnalyzer(scopes, m_errors))(*m_parserResult); + AsmAnalysisInfo analysisInfo; + return (AsmAnalyzer(analysisInfo, m_errors, _resolver)).analyze(*m_parserResult); } string InlineAssemblyStack::toString() @@ -59,14 +63,17 @@ string InlineAssemblyStack::toString() eth::Assembly InlineAssemblyStack::assemble() { - CodeGenerator codeGen(*m_parserResult, m_errors); - return codeGen.assemble(); + AsmAnalysisInfo analysisInfo; + AsmAnalyzer analyzer(analysisInfo, m_errors); + solAssert(analyzer.analyze(*m_parserResult), ""); + CodeGenerator codeGen(m_errors); + return codeGen.assemble(*m_parserResult, analysisInfo); } bool InlineAssemblyStack::parseAndAssemble( string const& _input, eth::Assembly& _assembly, - CodeGenerator::IdentifierAccess const& _identifierAccess + ExternalIdentifierAccess const& _identifierAccess ) { ErrorList errors; @@ -74,8 +81,12 @@ bool InlineAssemblyStack::parseAndAssemble( auto parserResult = Parser(errors).parse(scanner); if (!errors.empty()) return false; + solAssert(parserResult, ""); - CodeGenerator(*parserResult, errors).assemble(_assembly, _identifierAccess); + AsmAnalysisInfo analysisInfo; + AsmAnalyzer analyzer(analysisInfo, errors, _identifierAccess.resolve); + solAssert(analyzer.analyze(*parserResult), ""); + CodeGenerator(errors).assemble(*parserResult, analysisInfo, _assembly, _identifierAccess); // At this point, the assembly might be messed up, but we should throw an // internal compiler error anyway. diff --git a/libsolidity/inlineasm/AsmStack.h b/libsolidity/inlineasm/AsmStack.h index 4d5a99a4..77a7e02a 100644 --- a/libsolidity/inlineasm/AsmStack.h +++ b/libsolidity/inlineasm/AsmStack.h @@ -22,10 +22,10 @@ #pragma once +#include <libsolidity/interface/Exceptions.h> + #include <string> #include <functional> -#include <libsolidity/interface/Exceptions.h> -#include <libsolidity/inlineasm/AsmCodeGen.h> namespace dev { @@ -39,13 +39,34 @@ class Scanner; namespace assembly { struct Block; +struct Identifier; + +enum class IdentifierContext { LValue, RValue }; + +/// Object that is used to resolve references and generate code for access to identifiers external +/// to inline assembly (not used in standalone assembly mode). +struct ExternalIdentifierAccess +{ + using Resolver = std::function<size_t(assembly::Identifier const&, IdentifierContext)>; + /// Resolve a an external reference given by the identifier in the given context. + /// @returns the size of the value (number of stack slots) or size_t(-1) if not found. + Resolver resolve; + using CodeGenerator = std::function<void(assembly::Identifier const&, IdentifierContext, eth::Assembly&)>; + /// Generate code for retrieving the value (rvalue context) or storing the value (lvalue context) + /// of an identifier. The code should be appended to the assembly. In rvalue context, the value is supposed + /// to be put onto the stack, in lvalue context, the value is assumed to be at the top of the stack. + CodeGenerator generateCode; +}; class InlineAssemblyStack { public: /// Parse the given inline assembly chunk starting with `{` and ending with the corresponding `}`. /// @return false or error. - bool parse(std::shared_ptr<Scanner> const& _scanner); + bool parse( + std::shared_ptr<Scanner> const& _scanner, + ExternalIdentifierAccess::Resolver const& _externalIdentifierResolver = ExternalIdentifierAccess::Resolver() + ); /// Converts the parser result back into a string form (not necessarily the same form /// as the source form, but it should parse into the same parsed form again). std::string toString(); @@ -56,7 +77,7 @@ public: bool parseAndAssemble( std::string const& _input, eth::Assembly& _assembly, - CodeGenerator::IdentifierAccess const& _identifierAccess = CodeGenerator::IdentifierAccess() + ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess() ); ErrorList const& errors() const { return m_errors; } diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 6b0024ad..9c9c9614 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -38,6 +38,7 @@ #include <libsolidity/analysis/SyntaxChecker.h> #include <libsolidity/codegen/Compiler.h> #include <libsolidity/interface/InterfaceHandler.h> +#include <libsolidity/interface/GasEstimator.h> #include <libsolidity/formal/Why3Translator.h> #include <libevmasm/Exceptions.h> @@ -55,8 +56,8 @@ using namespace std; using namespace dev; using namespace dev::solidity; -CompilerStack::CompilerStack(ReadFileCallback const& _readFile): - m_readFile(_readFile), m_parseSuccessful(false) {} +CompilerStack::CompilerStack(ReadFile::Callback const& _readFile): + m_readFile(_readFile) {} void CompilerStack::setRemappings(vector<string> const& _remappings) { @@ -78,10 +79,12 @@ void CompilerStack::setRemappings(vector<string> const& _remappings) void CompilerStack::reset(bool _keepSources) { - m_parseSuccessful = false; if (_keepSources) + { + m_stackState = SourcesSet; for (auto sourcePair: m_sources) sourcePair.second.reset(); + } else { m_sources.clear(); @@ -93,6 +96,7 @@ void CompilerStack::reset(bool _keepSources) m_sourceOrder.clear(); m_contracts.clear(); m_errors.clear(); + m_stackState = Empty; } bool CompilerStack::addSource(string const& _name, string const& _content, bool _isLibrary) @@ -101,6 +105,7 @@ bool CompilerStack::addSource(string const& _name, string const& _content, bool reset(true); m_sources[_name].scanner = make_shared<Scanner>(CharStream(_content), _name); m_sources[_name].isLibrary = _isLibrary; + m_stackState = SourcesSet; return existed; } @@ -113,9 +118,10 @@ void CompilerStack::setSource(string const& _sourceCode) bool CompilerStack::parse() { //reset + if(m_stackState != SourcesSet) + return false; m_errors.clear(); ASTNode::resetID(); - m_parseSuccessful = false; if (SemVerVersion{string(VersionString)}.isPrerelease()) { @@ -127,14 +133,12 @@ bool CompilerStack::parse() vector<string> sourcesToParse; for (auto const& s: m_sources) sourcesToParse.push_back(s.first); - map<string, SourceUnit const*> sourceUnitsByName; for (size_t i = 0; i < sourcesToParse.size(); ++i) { string const& path = sourcesToParse[i]; Source& source = m_sources[path]; source.scanner->reset(); source.ast = Parser(m_errors).parse(source.scanner); - sourceUnitsByName[path] = source.ast.get(); if (!source.ast) solAssert(!Error::containsOnlyWarnings(m_errors), "Parser returned null but did not report error."); else @@ -149,10 +153,19 @@ bool CompilerStack::parse() } } } - if (!Error::containsOnlyWarnings(m_errors)) - // errors while parsing. should stop before type checking + if (Error::containsOnlyWarnings(m_errors)) + { + m_stackState = ParsingSuccessful; + return true; + } + else return false; +} +bool CompilerStack::analyze() +{ + if (m_stackState != ParsingSuccessful) + return false; resolveImports(); bool noErrors = true; @@ -172,6 +185,9 @@ bool CompilerStack::parse() if (!resolver.registerDeclarations(*source->ast)) return false; + map<string, SourceUnit const*> sourceUnitsByName; + for (auto& source: m_sources) + sourceUnitsByName[source.first] = source.second.ast.get(); for (Source const* source: m_sourceOrder) if (!resolver.performImports(*source->ast, sourceUnitsByName)) return false; @@ -234,8 +250,13 @@ bool CompilerStack::parse() noErrors = false; } - m_parseSuccessful = noErrors; - return m_parseSuccessful; + if (noErrors) + { + m_stackState = AnalysisSuccessful; + return true; + } + else + return false; } bool CompilerStack::parse(string const& _sourceCode) @@ -244,9 +265,20 @@ bool CompilerStack::parse(string const& _sourceCode) return parse(); } +bool CompilerStack::parseAndAnalyze() +{ + return parse() && analyze(); +} + +bool CompilerStack::parseAndAnalyze(std::string const& _sourceCode) +{ + setSource(_sourceCode); + return parseAndAnalyze(); +} + vector<string> CompilerStack::contractNames() const { - if (!m_parseSuccessful) + if (m_stackState < AnalysisSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); vector<string> contractNames; for (auto const& contract: m_contracts) @@ -257,8 +289,8 @@ vector<string> CompilerStack::contractNames() const bool CompilerStack::compile(bool _optimize, unsigned _runs, map<string, h160> const& _libraries) { - if (!m_parseSuccessful) - if (!parse()) + if (m_stackState < AnalysisSuccessful) + if (!parseAndAnalyze()) return false; m_optimize = _optimize; @@ -271,12 +303,13 @@ bool CompilerStack::compile(bool _optimize, unsigned _runs, map<string, h160> co if (auto contract = dynamic_cast<ContractDefinition const*>(node.get())) compileContract(*contract, compiledContracts); this->link(); + m_stackState = CompilationSuccessful; return true; } bool CompilerStack::compile(string const& _sourceCode, bool _optimize, unsigned _runs) { - return parse(_sourceCode) && compile(_optimize, _runs); + return parseAndAnalyze(_sourceCode) && compile(_optimize, _runs); } void CompilerStack::link() @@ -405,8 +438,9 @@ vector<string> CompilerStack::sourceNames() const map<string, unsigned> CompilerStack::sourceIndices() const { map<string, unsigned> indices; + unsigned index = 0; for (auto const& s: m_sources) - indices[s.first] = indices.size(); + indices[s.first] = index++; return indices; } @@ -417,7 +451,7 @@ Json::Value const& CompilerStack::interface(string const& _contractName) const Json::Value const& CompilerStack::metadata(string const& _contractName, DocumentationType _type) const { - if (!m_parseSuccessful) + if (m_stackState < AnalysisSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); return metadata(contract(_contractName), _type); @@ -425,7 +459,7 @@ Json::Value const& CompilerStack::metadata(string const& _contractName, Document Json::Value const& CompilerStack::metadata(Contract const& _contract, DocumentationType _type) const { - if (!m_parseSuccessful) + if (m_stackState < AnalysisSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); solAssert(_contract.contract, ""); @@ -456,7 +490,7 @@ Json::Value const& CompilerStack::metadata(Contract const& _contract, Documentat string const& CompilerStack::onChainMetadata(string const& _contractName) const { - if (!m_parseSuccessful) + if (m_stackState != CompilationSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); return contract(_contractName).onChainMetadata; @@ -522,18 +556,18 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string if (m_sources.count(importPath) || newSources.count(importPath)) continue; - ReadFileResult result{false, string("File not supplied initially.")}; + ReadFile::Result result{false, string("File not supplied initially.")}; if (m_readFile) result = m_readFile(importPath); if (result.success) - newSources[importPath] = result.contentsOrErrorMesage; + newSources[importPath] = result.contentsOrErrorMessage; else { auto err = make_shared<Error>(Error::Type::ParserError); *err << errinfo_sourceLocation(import->location()) << - errinfo_comment("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMesage); + errinfo_comment("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMessage); m_errors.push_back(std::move(err)); continue; } @@ -655,8 +689,33 @@ void CompilerStack::compileContract( cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2); compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata); compiledContract.compiler = compiler; - compiledContract.object = compiler->assembledObject(); - compiledContract.runtimeObject = compiler->runtimeObject(); + + try + { + compiledContract.object = compiler->assembledObject(); + } + catch(eth::OptimizerException const&) + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly optimizer exception for bytecode")); + } + catch(eth::AssemblyException const&) + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly exception for bytecode")); + } + + try + { + compiledContract.runtimeObject = compiler->runtimeObject(); + } + catch(eth::OptimizerException const&) + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly optimizer exception for deployed bytecode")); + } + catch(eth::AssemblyException const&) + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Assembly exception for deployed bytecode")); + } + compiledContract.onChainMetadata = onChainMetadata; _compiledContracts[compiledContract.contract] = &compiler->assembly(); @@ -841,3 +900,88 @@ string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) con } return ret; } + +namespace +{ + +Json::Value gasToJson(GasEstimator::GasConsumption const& _gas) +{ + if (_gas.isInfinite) + return Json::Value("infinite"); + else + return Json::Value(toString(_gas.value)); +} + +} + +Json::Value CompilerStack::gasEstimates(string const& _contractName) const +{ + if (!assemblyItems(_contractName) && !runtimeAssemblyItems(_contractName)) + return Json::Value(); + + using Gas = GasEstimator::GasConsumption; + Json::Value output(Json::objectValue); + + if (eth::AssemblyItems const* items = assemblyItems(_contractName)) + { + Gas executionGas = GasEstimator::functionalEstimation(*items); + u256 bytecodeSize(runtimeObject(_contractName).bytecode.size()); + Gas codeDepositGas = bytecodeSize * eth::GasCosts::createDataGas; + + Json::Value creation(Json::objectValue); + creation["codeDepositCost"] = gasToJson(codeDepositGas); + creation["executionCost"] = gasToJson(executionGas); + /// TODO: implement + overload to avoid the need of += + executionGas += codeDepositGas; + creation["totalCost"] = gasToJson(executionGas); + output["creation"] = creation; + } + + if (eth::AssemblyItems const* items = runtimeAssemblyItems(_contractName)) + { + /// External functions + ContractDefinition const& contract = contractDefinition(_contractName); + Json::Value externalFunctions(Json::objectValue); + for (auto it: contract.interfaceFunctions()) + { + string sig = it.second->externalSignature(); + externalFunctions[sig] = gasToJson(GasEstimator::functionalEstimation(*items, sig)); + } + + if (contract.fallbackFunction()) + /// This needs to be set to an invalid signature in order to trigger the fallback, + /// without the shortcut (of CALLDATSIZE == 0), and therefore to receive the upper bound. + /// An empty string ("") would work to trigger the shortcut only. + externalFunctions[""] = gasToJson(GasEstimator::functionalEstimation(*items, "INVALID")); + + if (!externalFunctions.empty()) + output["external"] = externalFunctions; + + /// Internal functions + Json::Value internalFunctions(Json::objectValue); + for (auto const& it: contract.definedFunctions()) + { + /// Exclude externally visible functions, constructor and the fallback function + if (it->isPartOfExternalInterface() || it->isConstructor() || it->name().empty()) + continue; + + size_t entry = functionEntryPoint(_contractName, *it); + GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite(); + if (entry > 0) + gas = GasEstimator::functionalEstimation(*items, entry, *it); + + FunctionType type(*it); + string sig = it->name() + "("; + auto paramTypes = type.parameterTypes(); + for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it) + sig += (*it)->toString() + (it + 1 == paramTypes.end() ? "" : ","); + sig += ")"; + internalFunctions[sig] = gasToJson(gas); + } + + if (!internalFunctions.empty()) + output["internal"] = internalFunctions; + } + + return output; +} diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index eddfea68..c1d344ca 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -36,6 +36,7 @@ #include <libevmasm/SourceLocation.h> #include <libevmasm/LinkerObject.h> #include <libsolidity/interface/Exceptions.h> +#include <libsolidity/interface/ReadFile.h> namespace dev { @@ -77,18 +78,10 @@ enum class DocumentationType: uint8_t class CompilerStack: boost::noncopyable { public: - struct ReadFileResult - { - bool success; - std::string contentsOrErrorMesage; - }; - - /// File reading callback. - using ReadFileCallback = std::function<ReadFileResult(std::string const&)>; - /// Creates a new compiler stack. - /// @param _readFile callback to used to read files for import statements. Should return - explicit CompilerStack(ReadFileCallback const& _readFile = ReadFileCallback()); + /// @param _readFile callback to used to read files for import statements. Must return + /// and must not emit exceptions. + explicit CompilerStack(ReadFile::Callback const& _readFile = ReadFile::Callback()); /// Sets path remappings in the format "context:prefix=target" void setRemappings(std::vector<std::string> const& _remappings); @@ -110,6 +103,16 @@ public: /// Sets the given source code as the only source unit apart from standard sources and parses it. /// @returns false on error. bool parse(std::string const& _sourceCode); + /// performs the analyisis steps (imports, scopesetting, syntaxCheck, referenceResolving, + /// typechecking, staticAnalysis) on previously set sources + /// @returns false on error. + bool analyze(); + /// Parses and analyzes all source units that were added + /// @returns false on error. + bool parseAndAnalyze(); + /// Sets the given source code as the only source unit apart from standard sources and parses and analyzes it. + /// @returns false on error. + bool parseAndAnalyze(std::string const& _sourceCode); /// @returns a list of the contract names in the sources. std::vector<std::string> contractNames() const; std::string defaultContractName() const; @@ -181,6 +184,9 @@ public: std::string const& onChainMetadata(std::string const& _contractName) const; void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; } + /// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions + Json::Value gasEstimates(std::string const& _contractName) const; + /// @returns the previously used scanner, useful for counting lines during error reporting. Scanner const& scanner(std::string const& _sourceName = "") const; /// @returns the parsed source unit with the supplied name. @@ -230,6 +236,13 @@ private: mutable std::unique_ptr<std::string const> sourceMapping; mutable std::unique_ptr<std::string const> runtimeSourceMapping; }; + enum State { + Empty, + SourcesSet, + ParsingSuccessful, + AnalysisSuccessful, + CompilationSuccessful + }; /// Loads the missing sources from @a _ast (named @a _path) using the callback /// @a m_readFile and stores the absolute paths of all imports in the AST annotations. @@ -263,14 +276,13 @@ private: std::string target; }; - ReadFileCallback m_readFile; + ReadFile::Callback m_readFile; bool m_optimize = false; unsigned m_optimizeRuns = 200; std::map<std::string, h160> m_libraries; /// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum /// "context:prefix=target" std::vector<Remapping> m_remappings; - bool m_parseSuccessful; std::map<std::string const, Source> m_sources; std::shared_ptr<GlobalContext> m_globalContext; std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes; @@ -279,6 +291,7 @@ private: std::string m_formalTranslation; ErrorList m_errors; bool m_metadataLiteralSources = false; + State m_stackState = Empty; }; } diff --git a/libsolidity/interface/Exceptions.cpp b/libsolidity/interface/Exceptions.cpp index 968a24ad..c09180de 100644 --- a/libsolidity/interface/Exceptions.cpp +++ b/libsolidity/interface/Exceptions.cpp @@ -33,22 +33,22 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip switch(m_type) { case Type::DeclarationError: - m_typeName = "Declaration Error"; + m_typeName = "DeclarationError"; break; case Type::DocstringParsingError: - m_typeName = "Docstring Parsing Error"; + m_typeName = "DocstringParsingError"; break; case Type::ParserError: - m_typeName = "Parser Error"; + m_typeName = "ParserError"; break; case Type::SyntaxError: - m_typeName = "Syntax Error"; + m_typeName = "SyntaxError"; break; case Type::TypeError: - m_typeName = "Type Error"; + m_typeName = "TypeError"; break; case Type::Why3TranslatorError: - m_typeName = "Why3 Translator Error"; + m_typeName = "Why3TranslatorError"; break; case Type::Warning: m_typeName = "Warning"; diff --git a/libsolidity/interface/ReadFile.h b/libsolidity/interface/ReadFile.h new file mode 100644 index 00000000..2e8a6bd8 --- /dev/null +++ b/libsolidity/interface/ReadFile.h @@ -0,0 +1,45 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <string> +#include <functional> +#include <boost/noncopyable.hpp> + +namespace dev +{ + +namespace solidity +{ + +class ReadFile: boost::noncopyable +{ +public: + /// File reading result. + struct Result + { + bool success; + std::string contentsOrErrorMessage; + }; + + /// File reading callback. + using Callback = std::function<Result(std::string const&)>; +}; + +} +} diff --git a/libsolidity/interface/SourceReferenceFormatter.h b/libsolidity/interface/SourceReferenceFormatter.h index 7034f4ab..e8676d60 100644 --- a/libsolidity/interface/SourceReferenceFormatter.h +++ b/libsolidity/interface/SourceReferenceFormatter.h @@ -23,6 +23,7 @@ #pragma once #include <ostream> +#include <sstream> #include <functional> #include <libevmasm/SourceLocation.h> @@ -53,6 +54,16 @@ public: std::string const& _name, ScannerFromSourceNameFun const& _scannerFromSourceName ); + static std::string formatExceptionInformation( + Exception const& _exception, + std::string const& _name, + ScannerFromSourceNameFun const& _scannerFromSourceName + ) + { + std::ostringstream errorOutput; + printExceptionInformation(errorOutput, _exception, _name, _scannerFromSourceName); + return errorOutput.str(); + } private: /// Prints source name if location is given. static void printSourceName( diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp new file mode 100644 index 00000000..223cc15d --- /dev/null +++ b/libsolidity/interface/StandardCompiler.cpp @@ -0,0 +1,482 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Alex Beregszaszi + * @date 2016 + * Standard JSON compiler interface. + */ + +#include <libsolidity/interface/StandardCompiler.h> +#include <libsolidity/interface/SourceReferenceFormatter.h> +#include <libsolidity/ast/ASTJsonConverter.h> +#include <libevmasm/Instruction.h> +#include <libdevcore/JSON.h> +#include <libdevcore/SHA3.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +namespace { + +Json::Value formatError( + bool _warning, + string const& _type, + string const& _component, + string const& _message, + string const& _formattedMessage = "", + Json::Value const& _sourceLocation = Json::Value() +) +{ + Json::Value error = Json::objectValue; + error["type"] = _type; + error["component"] = _component; + error["severity"] = _warning ? "warning" : "error"; + error["message"] = _message; + error["formattedMessage"] = (_formattedMessage.length() > 0) ? _formattedMessage : _message; + if (_sourceLocation.isObject()) + error["sourceLocation"] = _sourceLocation; + return error; +} + +Json::Value formatFatalError(string const& _type, string const& _message) +{ + Json::Value output = Json::objectValue; + output["errors"] = Json::arrayValue; + output["errors"].append(formatError(false, _type, "general", _message)); + return output; +} + +Json::Value formatErrorWithException( + Exception const& _exception, + bool const& _warning, + string const& _type, + string const& _component, + string const& _message, + function<Scanner const&(string const&)> const& _scannerFromSourceName +) +{ + string message; + string formattedMessage = SourceReferenceFormatter::formatExceptionInformation(_exception, _message, _scannerFromSourceName); + + // NOTE: the below is partially a copy from SourceReferenceFormatter + SourceLocation const* location = boost::get_error_info<errinfo_sourceLocation>(_exception); + + if (string const* description = boost::get_error_info<errinfo_comment>(_exception)) + message = ((_message.length() > 0) ? (_message + ":") : "") + *description; + else + message = _message; + + if (location && location->sourceName) + { + Json::Value sourceLocation = Json::objectValue; + sourceLocation["file"] = *location->sourceName; + sourceLocation["start"] = location->start; + sourceLocation["end"] = location->end; + } + + return formatError(_warning, _type, _component, message, formattedMessage, location); +} + +/// Returns true iff @a _hash (hex with 0x prefix) is the Keccak256 hash of the binary data in @a _content. +bool hashMatchesContent(string const& _hash, string const& _content) +{ + try + { + return dev::h256(_hash) == dev::keccak256(_content); + } + catch (dev::BadHexCharacter) + { + return false; + } +} + +StringMap createSourceList(Json::Value const& _input) +{ + StringMap sources; + Json::Value const& jsonSources = _input["sources"]; + if (jsonSources.isObject()) + for (auto const& sourceName: jsonSources.getMemberNames()) + sources[sourceName] = jsonSources[sourceName]["content"].asString(); + return sources; +} + +Json::Value methodIdentifiers(ContractDefinition const& _contract) +{ + Json::Value methodIdentifiers(Json::objectValue); + for (auto const& it: _contract.interfaceFunctions()) + methodIdentifiers[it.second->externalSignature()] = toHex(it.first.ref()); + return methodIdentifiers; +} + +Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkReferences) +{ + Json::Value ret(Json::objectValue); + + for (auto const& ref: linkReferences) + { + string const& fullname = ref.second; + size_t colon = fullname.find(':'); + solAssert(colon != string::npos, ""); + string file = fullname.substr(0, colon); + string name = fullname.substr(colon + 1); + + Json::Value fileObject = ret.get(file, Json::objectValue); + Json::Value libraryArray = fileObject.get(name, Json::arrayValue); + + Json::Value entry = Json::objectValue; + entry["start"] = Json::UInt(ref.first); + entry["length"] = 20; + + libraryArray.append(entry); + fileObject[name] = libraryArray; + ret[file] = fileObject; + } + + return ret; +} + +Json::Value collectEVMObject(eth::LinkerObject const& _object, string const* _sourceMap) +{ + Json::Value output = Json::objectValue; + output["object"] = _object.toHex(); + output["opcodes"] = solidity::disassemble(_object.bytecode); + output["sourceMap"] = _sourceMap ? *_sourceMap : ""; + output["linkReferences"] = formatLinkReferences(_object.linkReferences); + return output; +} + +} + +Json::Value StandardCompiler::compileInternal(Json::Value const& _input) +{ + m_compilerStack.reset(false); + + if (!_input.isObject()) + return formatFatalError("JSONError", "Input is not a JSON object."); + + if (_input["language"] != "Solidity") + return formatFatalError("JSONError", "Only \"Solidity\" is supported as a language."); + + Json::Value const& sources = _input["sources"]; + if (!sources) + return formatFatalError("JSONError", "No input sources specified."); + + Json::Value errors = Json::arrayValue; + + for (auto const& sourceName: sources.getMemberNames()) + { + string hash; + + if (!sources[sourceName].isObject()) + return formatFatalError("JSONError", "Source input is not a JSON object."); + + if (sources[sourceName]["keccak256"].isString()) + hash = sources[sourceName]["keccak256"].asString(); + + if (sources[sourceName]["content"].isString()) + { + string content = sources[sourceName]["content"].asString(); + if (!hash.empty() && !hashMatchesContent(hash, content)) + errors.append(formatError( + false, + "IOError", + "general", + "Mismatch between content and supplied hash for \"" + sourceName + "\"" + )); + else + m_compilerStack.addSource(sourceName, content); + } + else if (sources[sourceName]["urls"].isArray()) + { + if (!m_readFile) + return formatFatalError("JSONError", "No import callback supplied, but URL is requested."); + + bool found = false; + vector<string> failures; + + for (auto const& url: sources[sourceName]["urls"]) + { + ReadFile::Result result = m_readFile(url.asString()); + if (result.success) + { + if (!hash.empty() && !hashMatchesContent(hash, result.contentsOrErrorMessage)) + errors.append(formatError( + false, + "IOError", + "general", + "Mismatch between content and supplied hash for \"" + sourceName + "\" at \"" + url.asString() + "\"" + )); + else + { + m_compilerStack.addSource(sourceName, result.contentsOrErrorMessage); + found = true; + break; + } + } + else + failures.push_back("Cannot import url (\"" + url.asString() + "\"): " + result.contentsOrErrorMessage); + } + + for (auto const& failure: failures) + { + /// If the import succeeded, let mark all the others as warnings, otherwise all of them are errors. + errors.append(formatError( + found ? true : false, + "IOError", + "general", + failure + )); + } + } + else + return formatFatalError("JSONError", "Invalid input source specified."); + } + + Json::Value const& settings = _input.get("settings", Json::Value()); + + vector<string> remappings; + for (auto const& remapping: settings.get("remappings", Json::Value())) + remappings.push_back(remapping.asString()); + m_compilerStack.setRemappings(remappings); + + Json::Value optimizerSettings = settings.get("optimizer", Json::Value()); + bool optimize = optimizerSettings.get("enabled", Json::Value(false)).asBool(); + unsigned optimizeRuns = optimizerSettings.get("runs", Json::Value(200u)).asUInt(); + + map<string, h160> libraries; + Json::Value jsonLibraries = settings.get("libraries", Json::Value()); + for (auto const& sourceName: jsonLibraries.getMemberNames()) + { + auto const& jsonSourceName = jsonLibraries[sourceName]; + for (auto const& library: jsonSourceName.getMemberNames()) + // @TODO use libraries only for the given source + libraries[library] = h160(jsonSourceName[library].asString()); + } + + Json::Value metadataSettings = settings.get("metadata", Json::Value()); + m_compilerStack.useMetadataLiteralSources(metadataSettings.get("useLiteralContent", Json::Value(false)).asBool()); + + auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compilerStack.scanner(_sourceName); }; + + bool success = false; + + try + { + success = m_compilerStack.compile(optimize, optimizeRuns, libraries); + + for (auto const& error: m_compilerStack.errors()) + { + auto err = dynamic_pointer_cast<Error const>(error); + + errors.append(formatErrorWithException( + *error, + err->type() == Error::Type::Warning, + err->typeName(), + "general", + "", + scannerFromSourceName + )); + } + } + catch (Error const& _error) + { + if (_error.type() == Error::Type::DocstringParsingError) + errors.append(formatError( + false, + "DocstringParsingError", + "general", + "Documentation parsing error: " + *boost::get_error_info<errinfo_comment>(_error) + )); + else + errors.append(formatErrorWithException( + _error, + false, + _error.typeName(), + "general", + "", + scannerFromSourceName + )); + } + catch (CompilerError const& _exception) + { + errors.append(formatErrorWithException( + _exception, + false, + "CompilerError", + "general", + "Compiler error (" + _exception.lineInfo() + ")", + scannerFromSourceName + )); + } + catch (InternalCompilerError const& _exception) + { + errors.append(formatErrorWithException( + _exception, + false, + "InternalCompilerError", + "general", + "Internal compiler error (" + _exception.lineInfo() + ")", scannerFromSourceName + )); + } + catch (UnimplementedFeatureError const& _exception) + { + errors.append(formatErrorWithException( + _exception, + false, + "UnimplementedFeatureError", + "general", + "Unimplemented feature (" + _exception.lineInfo() + ")", + scannerFromSourceName)); + } + catch (Exception const& _exception) + { + errors.append(formatError( + false, + "Exception", + "general", + "Exception during compilation: " + boost::diagnostic_information(_exception) + )); + } + catch (...) + { + errors.append(formatError( + false, + "Exception", + "general", + "Unknown exception during compilation." + )); + } + + Json::Value output = Json::objectValue; + + if (errors.size() > 0) + output["errors"] = errors; + + /// Inconsistent state - stop here to receive error reports from users + if (!success && (errors.size() == 0)) + return formatFatalError("InternalCompilerError", "No error reported, but compilation failed."); + + output["sources"] = Json::objectValue; + unsigned sourceIndex = 0; + for (auto const& source: m_compilerStack.sourceNames()) + { + Json::Value sourceResult = Json::objectValue; + sourceResult["id"] = sourceIndex++; + sourceResult["legacyAST"] = ASTJsonConverter(m_compilerStack.ast(source), m_compilerStack.sourceIndices()).json(); + output["sources"][source] = sourceResult; + } + + Json::Value contractsOutput = Json::objectValue; + for (string const& contractName: success ? m_compilerStack.contractNames() : vector<string>()) + { + size_t colon = contractName.find(':'); + solAssert(colon != string::npos, ""); + string file = contractName.substr(0, colon); + string name = contractName.substr(colon + 1); + + // ABI, documentation and metadata + Json::Value contractData(Json::objectValue); + contractData["abi"] = m_compilerStack.metadata(contractName, DocumentationType::ABIInterface); + contractData["metadata"] = m_compilerStack.onChainMetadata(contractName); + contractData["userdoc"] = m_compilerStack.metadata(contractName, DocumentationType::NatspecUser); + contractData["devdoc"] = m_compilerStack.metadata(contractName, DocumentationType::NatspecDev); + + // EVM + Json::Value evmData(Json::objectValue); + // @TODO: add ir + ostringstream tmp; + m_compilerStack.streamAssembly(tmp, contractName, createSourceList(_input), false); + evmData["assembly"] = tmp.str(); + evmData["legacyAssembly"] = m_compilerStack.streamAssembly(tmp, contractName, createSourceList(_input), true); + evmData["methodIdentifiers"] = methodIdentifiers(m_compilerStack.contractDefinition(contractName)); + evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName); + + evmData["bytecode"] = collectEVMObject( + m_compilerStack.object(contractName), + m_compilerStack.sourceMapping(contractName) + ); + + evmData["deployedBytecode"] = collectEVMObject( + m_compilerStack.runtimeObject(contractName), + m_compilerStack.runtimeSourceMapping(contractName) + ); + + contractData["evm"] = evmData; + + if (!contractsOutput.isMember(file)) + contractsOutput[file] = Json::objectValue; + + contractsOutput[file][name] = contractData; + } + output["contracts"] = contractsOutput; + + return output; +} + +Json::Value StandardCompiler::compile(Json::Value const& _input) +{ + try + { + return compileInternal(_input); + } + catch (Json::LogicError const& _exception) + { + return formatFatalError("InternalCompilerError", string("JSON logic exception: ") + _exception.what()); + } + catch (Json::RuntimeError const& _exception) + { + return formatFatalError("InternalCompilerError", string("JSON runtime exception: ") + _exception.what()); + } + catch (Exception const& _exception) + { + return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal: " + boost::diagnostic_information(_exception)); + } + catch (...) + { + return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal"); + } +} + +string StandardCompiler::compile(string const& _input) +{ + Json::Value input; + Json::Reader reader; + + try + { + if (!reader.parse(_input, input, false)) + return jsonCompactPrint(formatFatalError("JSONError", reader.getFormattedErrorMessages())); + } + catch(...) + { + return "{\"errors\":\"[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error parsing input JSON.\"}]}"; + } + + // cout << "Input: " << input.toStyledString() << endl; + Json::Value output = compile(input); + // cout << "Output: " << output.toStyledString() << endl; + + try + { + return jsonCompactPrint(output); + } + catch(...) + { + return "{\"errors\":\"[{\"type\":\"JSONError\",\"component\":\"general\",\"severity\":\"error\",\"message\":\"Error writing output JSON.\"}]}"; + } +} diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h new file mode 100644 index 00000000..dfaf88cd --- /dev/null +++ b/libsolidity/interface/StandardCompiler.h @@ -0,0 +1,63 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Alex Beregszaszi + * @date 2016 + * Standard JSON compiler interface. + */ + +#pragma once + +#include <libsolidity/interface/CompilerStack.h> + +namespace dev +{ + +namespace solidity +{ + +/** + * Standard JSON compiler interface, which expects a JSON input and returns a JSON ouput. + * See docs/using-the-compiler#compiler-input-and-output-json-description. + */ +class StandardCompiler: boost::noncopyable +{ +public: + /// Creates a new StandardCompiler. + /// @param _readFile callback to used to read files for import statements. Must return + /// and must not emit exceptions. + StandardCompiler(ReadFile::Callback const& _readFile = ReadFile::Callback()) + : m_compilerStack(_readFile), m_readFile(_readFile) + { + } + + /// Sets all input parameters according to @a _input which conforms to the standardized input + /// format, performs compilation and returns a standardized output. + Json::Value compile(Json::Value const& _input); + /// Parses input as JSON and peforms the above processing steps, returning a serialized JSON + /// output. Parsing errors are returned as regular errors. + std::string compile(std::string const& _input); + +private: + Json::Value compileInternal(Json::Value const& _input); + + CompilerStack m_compilerStack; + ReadFile::Callback m_readFile; +}; + +} +} diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index e26e2908..b5130c8a 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -82,9 +82,10 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner) case Token::Import: nodes.push_back(parseImportDirective()); break; + case Token::Interface: case Token::Contract: case Token::Library: - nodes.push_back(parseContractDefinition(token == Token::Library)); + nodes.push_back(parseContractDefinition(token)); break; default: fatalParserError(string("Expected import directive or contract definition.")); @@ -193,13 +194,30 @@ ASTPointer<ImportDirective> Parser::parseImportDirective() return nodeFactory.createNode<ImportDirective>(path, unitAlias, move(symbolAliases)); } -ASTPointer<ContractDefinition> Parser::parseContractDefinition(bool _isLibrary) +ContractDefinition::ContractKind Parser::tokenToContractKind(Token::Value _token) +{ + switch(_token) + { + case Token::Interface: + return ContractDefinition::ContractKind::Interface; + case Token::Contract: + return ContractDefinition::ContractKind::Contract; + case Token::Library: + return ContractDefinition::ContractKind::Library; + default: + fatalParserError("Unsupported contract type."); + } + // FIXME: fatalParserError is not considered as throwing here + return ContractDefinition::ContractKind::Contract; +} + +ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _expectedKind) { ASTNodeFactory nodeFactory(*this); ASTPointer<ASTString> docString; if (m_scanner->currentCommentLiteral() != "") docString = make_shared<ASTString>(m_scanner->currentCommentLiteral()); - expectToken(_isLibrary ? Token::Library : Token::Contract); + expectToken(_expectedKind); ASTPointer<ASTString> name = expectIdentifierToken(); vector<ASTPointer<InheritanceSpecifier>> baseContracts; if (m_scanner->currentToken() == Token::Is) @@ -252,7 +270,7 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(bool _isLibrary) docString, baseContracts, subNodes, - _isLibrary + tokenToContractKind(_expectedKind) ); } diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 79d1d1d4..282617ab 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -69,7 +69,8 @@ private: ///@name Parsing functions for the AST nodes ASTPointer<PragmaDirective> parsePragmaDirective(); ASTPointer<ImportDirective> parseImportDirective(); - ASTPointer<ContractDefinition> parseContractDefinition(bool _isLibrary); + ContractDefinition::ContractKind tokenToContractKind(Token::Value _token); + ASTPointer<ContractDefinition> parseContractDefinition(Token::Value _expectedKind); ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier(); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h index c6d050bb..9a557ebd 100644 --- a/libsolidity/parsing/Token.h +++ b/libsolidity/parsing/Token.h @@ -157,6 +157,7 @@ namespace solidity K(Hex, "hex", 0) \ K(If, "if", 0) \ K(Indexed, "indexed", 0) \ + K(Interface, "interface", 0) \ K(Internal, "internal", 0) \ K(Import, "import", 0) \ K(Is, "is", 0) \ @@ -225,7 +226,6 @@ namespace solidity K(Final, "final", 0) \ K(In, "in", 0) \ K(Inline, "inline", 0) \ - K(Interface, "interface", 0) \ K(Let, "let", 0) \ K(Match, "match", 0) \ K(NullLiteral, "null", 0) \ |