diff options
33 files changed, 1645 insertions, 515 deletions
diff --git a/Changelog.md b/Changelog.md index 7b64f950..cd54aadb 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,8 @@ Features: * Commandline interface: Add the ``--standard-json`` parameter to process a Standard JSON I/O. * Commandline interface: Support ``--allow-paths`` to define trusted import paths. Note: the path(s) of the supplied source file(s) is always trusted. + * Inline Assembly: Storage variable access using ``_slot`` and ``_offset`` suffixes. + * Inline Assembly: Disallow blocks with unbalanced stack. * Static analyzer: Warn about statements without effects. Bugfixes: diff --git a/docs/assembly.rst b/docs/assembly.rst index 30f7fc01..420cea17 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -29,7 +29,7 @@ arising when writing manual assembly by the following features: * labels: ``let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))`` * loops: ``for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }`` * switch statements: ``switch x case 0: { y := mul(x, 2) } default: { y := 0 }`` -* function calls: ``function f(x) -> (y) { switch x case 0: { y := 1 } default: { y := mul(x, f(sub(x, 1))) } }`` +* function calls: ``function f(x) -> y { switch x case 0: { y := 1 } default: { y := mul(x, f(sub(x, 1))) } }`` .. note:: Of the above, loops, function calls and switch statements are not yet implemented. @@ -323,9 +323,12 @@ Access to External Variables and Functions ------------------------------------------ Solidity variables and other identifiers can be accessed by simply using their name. -For storage and memory variables, this will push the address and not the value onto the -stack. Also note that non-struct and non-array storage variable addresses occupy two slots -on the stack: One for the address and one for the byte offset inside the storage slot. +For memory variables, this will push the address and not the value onto the +stack. Storage variables are different: Values in storage might not occupy a +full storage slot, so their "address" is composed of a slot and a byte-offset +inside that slot. To retrieve the slot pointed to by the variable ``x``, you +used ``x_slot`` and to retrieve the byte-offset you used ``x_offset``. + In assignments (see below), we can even use local Solidity variables to assign to. Functions external to inline assembly can also be accessed: The assembly will @@ -340,17 +343,13 @@ changes during the call, and thus references to local variables will be wrong. .. code:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.11; contract C { uint b; function f(uint x) returns (uint r) { assembly { - b pop // remove the offset, we know it is zero - sload - x - mul - =: r // assign to return variable r + r := mul(x, sload(b_slot)) // ignore the offset, we know it is zero } } } @@ -567,7 +566,7 @@ The following example implements the power function by square-and-multiply. .. code:: assembly { - function power(base, exponent) -> (result) { + function power(base, exponent) -> result { switch exponent 0: { result := 1 } 1: { result := base } @@ -702,12 +701,12 @@ The following assembly will be generated:: } default: { jump(invalidJumpLabel) } // memory allocator - function $allocate(size) -> (pos) { + function $allocate(size) -> pos { pos := mload(0x40) mstore(0x40, add(pos, size)) } // the contract function - function f(x) -> (y) { + function f(x) -> y { y := 1 for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 2865d884..914dfacd 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -314,6 +314,14 @@ Comments are of course also not permitted and used here only for explanatory pur } } +.. note:: + Note the ABI definition above has no fixed order. It can change with compiler versions. + +.. note:: + Since the bytecode of the resulting contract contains the metadata hash, any change to + the metadata will result in a change of the bytecode. Furthermore, since the metadata + includes a hash of all the sources used, a single whitespace change in any of the source + codes will result in a different metadata, and subsequently a different bytecode. Encoding of the Metadata Hash in the Bytecode ============================================= diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index deaa1329..b74da213 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -251,15 +251,15 @@ Output Description // Function gas estimates gasEstimates: { creation: { - dataCost: 420000, - // -1 means infinite (aka. unknown) - executionCost: -1 + codeDepositCost: "420000", + executionCost: "infinite", + totalCost: "infinite" }, external: { - "delegate(address)": 25000 + "delegate(address)": "25000" }, internal: { - "heavyLifting()": -1 + "heavyLifting()": "infinite" } } }, diff --git a/libevmasm/ConstantOptimiser.cpp b/libevmasm/ConstantOptimiser.cpp index a1dfd21c..d2ed4faf 100644 --- a/libevmasm/ConstantOptimiser.cpp +++ b/libevmasm/ConstantOptimiser.cpp @@ -232,6 +232,54 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value) } } +bool ComputeMethod::checkRepresentation(u256 const& _value, AssemblyItems const& _routine) +{ + // This is a tiny EVM that can only evaluate some instructions. + vector<u256> stack; + for (AssemblyItem const& item: _routine) + { + switch (item.type()) + { + case Operation: + { + if (stack.size() < size_t(item.arguments())) + return false; + u256* sp = &stack.back(); + switch (item.instruction()) + { + case Instruction::MUL: + sp[-1] = sp[0] * sp[-1]; + break; + case Instruction::EXP: + if (sp[-1] > 0xff) + return false; + sp[-1] = boost::multiprecision::pow(sp[0], unsigned(sp[-1])); + break; + case Instruction::ADD: + sp[-1] = sp[0] + sp[-1]; + break; + case Instruction::SUB: + sp[-1] = sp[0] - sp[-1]; + break; + case Instruction::NOT: + sp[0] = ~sp[0]; + break; + default: + return false; + } + stack.resize(stack.size() + item.deposit()); + break; + } + case Push: + stack.push_back(item.data()); + break; + default: + return false; + } + } + return stack.size() == 1 && stack.front() == _value; +} + bigint ComputeMethod::gasNeeded(AssemblyItems const& _routine) { size_t numExps = count(_routine.begin(), _routine.end(), Instruction::EXP); diff --git a/libevmasm/ConstantOptimiser.h b/libevmasm/ConstantOptimiser.h index 4f12c49f..85bdabac 100644 --- a/libevmasm/ConstantOptimiser.h +++ b/libevmasm/ConstantOptimiser.h @@ -21,10 +21,14 @@ #pragma once -#include <vector> +#include <libevmasm/Exceptions.h> + +#include <libdevcore/Assertions.h> #include <libdevcore/CommonData.h> #include <libdevcore/CommonIO.h> +#include <vector> + namespace dev { namespace eth @@ -130,6 +134,11 @@ public: ConstantOptimisationMethod(_params, _value) { m_routine = findRepresentation(m_value); + assertThrow( + checkRepresentation(m_value, m_routine), + OptimizerException, + "Invalid constant expression created." + ); } virtual bigint gasNeeded() override { return gasNeeded(m_routine); } @@ -141,6 +150,8 @@ public: protected: /// Tries to recursively find a way to compute @a _value. AssemblyItems findRepresentation(u256 const& _value); + /// Recomputes the value from the calculated representation and checks for correctness. + bool checkRepresentation(u256 const& _value, AssemblyItems const& _routine); bigint gasNeeded(AssemblyItems const& _routine); /// Counter for the complexity of optimization, will stop when it reaches zero. 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/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index b37db7b7..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; @@ -628,65 +629,91 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType) 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)) + { + } + else if (dynamic_cast<VariableDeclaration 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 (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; } 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/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/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/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 fdfd9abe..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) { @@ -132,6 +125,7 @@ public: solAssert(_literal.value.size() <= 32, ""); 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 bda7a882..79855060 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -686,8 +686,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(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4d56ec9d..01a9a188 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -21,4 +21,4 @@ include_directories(BEFORE ..) target_link_libraries(${EXECUTABLE} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) add_executable(solfuzzer fuzzer.cpp) -target_link_libraries(solfuzzer soljson) +target_link_libraries(solfuzzer soljson ${Boost_PROGRAM_OPTIONS_LIBRARIES}) diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index caf09a91..6dc3f77f 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -79,7 +79,7 @@ TMPDIR=$(mktemp -d) "$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/test/ for f in *.sol do - "$REPO_ROOT"/build/test/solfuzzer < "$f" + "$REPO_ROOT"/build/test/solfuzzer --quiet < "$f" done ) rm -rf "$TMPDIR" diff --git a/test/fuzzer.cpp b/test/fuzzer.cpp index 410313c5..afa50671 100644 --- a/test/fuzzer.cpp +++ b/test/fuzzer.cpp @@ -16,21 +16,32 @@ */ /** * Executable for use with AFL <http://lcamtuf.coredump.cx/afl>. - * Reads a single source from stdin and signals a failure for internal errors. */ +#include <libevmasm/Assembly.h> +#include <libevmasm/ConstantOptimiser.h> + #include <json/json.h> +#include <boost/program_options.hpp> + #include <string> #include <iostream> using namespace std; +using namespace dev; +using namespace dev::eth; +namespace po = boost::program_options; extern "C" { extern char const* compileJSON(char const* _input, bool _optimize); +typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error); +extern char const* compileStandard(char const* _input, CStyleReadFileCallback _readCallback); } +bool quiet = false; + string contains(string const& _haystack, vector<string> const& _needles) { for (string const& needle: _needles) @@ -39,7 +50,42 @@ string contains(string const& _haystack, vector<string> const& _needles) return ""; } -int main() +void testConstantOptimizer() +{ + if (!quiet) + cout << "Testing constant optimizer" << endl; + vector<u256> numbers; + while (!cin.eof()) + { + h256 data; + cin.read(reinterpret_cast<char*>(data.data()), 32); + numbers.push_back(u256(data)); + } + if (!quiet) + cout << "Got " << numbers.size() << " inputs:" << endl; + + Assembly assembly; + for (u256 const& n: numbers) + { + if (!quiet) + cout << n << endl; + assembly.append(n); + } + for (bool isCreation: {false, true}) + { + for (unsigned runs: {1, 2, 3, 20, 40, 100, 200, 400, 1000}) + { + ConstantOptimisationMethod::optimiseConstants( + isCreation, + runs, + assembly, + const_cast<AssemblyItems&>(assembly.items()) + ); + } + } +} + +string readInput() { string input; while (!cin.eof()) @@ -48,6 +94,41 @@ int main() getline(cin, s); input += s + '\n'; } + return input; +} + +void testStandardCompiler() +{ + if (!quiet) + cout << "Testing compiler via JSON interface." << endl; + string input = readInput(); + string outputString(compileStandard(input.c_str(), NULL)); + Json::Value output; + if (!Json::Reader().parse(outputString, output)) + { + cout << "Compiler produced invalid JSON output." << endl; + abort(); + } + if (output.isMember("errors")) + for (auto const& error: output["errors"]) + { + string invalid = contains(error["type"].asString(), vector<string>{ + "Exception", + "InternalCompilerError" + }); + if (!invalid.empty()) + { + cout << "Invalid error: \"" << error["type"].asString() << "\"" << endl; + abort(); + } + } +} + +void testCompiler() +{ + if (!quiet) + cout << "Testing compiler." << endl; + string input = readInput(); bool optimize = true; string outputString(compileJSON(input.c_str(), optimize)); @@ -87,5 +168,57 @@ int main() cout << "Output JSON has neither \"errors\" nor \"contracts\"." << endl; abort(); } +} + +int main(int argc, char** argv) +{ + po::options_description options( + R"(solfuzzer, fuzz-testing binary for use with AFL. +Usage: solfuzzer [Options] < input +Reads a single source from stdin, compiles it and signals a failure for internal errors. + +Allowed options)", + po::options_description::m_default_line_length, + po::options_description::m_default_line_length - 23); + options.add_options() + ("help", "Show this help screen.") + ("quiet", "Only output errors.") + ( + "standard-json", + "Test via the standard-json interface, i.e. " + "input is expected to be JSON-encoded instead of " + "plain source file." + ) + ( + "const-opt", + "Run the constant optimizer instead of compiling. " + "Expects a binary string of up to 32 bytes on stdin." + ); + + po::variables_map arguments; + try + { + po::command_line_parser cmdLineParser(argc, argv); + cmdLineParser.options(options); + po::store(cmdLineParser.run(), arguments); + } + catch (po::error const& _exception) + { + cerr << _exception.what() << endl; + return false; + } + + if (arguments.count("quiet")) + quiet = true; + + if (arguments.count("help")) + cout << options; + else if (arguments.count("const-opt")) + testConstantOptimizer(); + else if (arguments.count("standard-json")) + testStandardCompiler(); + else + testCompiler(); + return 0; } diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 9035599b..8bf4df8e 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -30,6 +30,7 @@ #include <libevmasm/Assembly.h> #include <boost/optional.hpp> +#include <boost/algorithm/string/replace.hpp> #include <string> #include <memory> @@ -63,7 +64,7 @@ boost::optional<Error> parseAndReturnFirstError(string const& _source, bool _ass } if (!success) { - BOOST_CHECK_EQUAL(stack.errors().size(), 1); + BOOST_REQUIRE_EQUAL(stack.errors().size(), 1); return *stack.errors().front(); } else @@ -137,22 +138,22 @@ BOOST_AUTO_TEST_CASE(smoke_test) BOOST_AUTO_TEST_CASE(simple_instructions) { - BOOST_CHECK(successParse("{ dup1 dup1 mul dup1 sub }")); + BOOST_CHECK(successParse("{ dup1 dup1 mul dup1 sub pop }")); } BOOST_AUTO_TEST_CASE(suicide_selfdestruct) { - BOOST_CHECK(successParse("{ suicide selfdestruct }")); + BOOST_CHECK(successParse("{ 0x01 suicide 0x02 selfdestruct }")); } BOOST_AUTO_TEST_CASE(keywords) { - BOOST_CHECK(successParse("{ byte return address }")); + BOOST_CHECK(successParse("{ 1 2 byte 2 return address pop }")); } BOOST_AUTO_TEST_CASE(constants) { - BOOST_CHECK(successParse("{ 7 8 mul }")); + BOOST_CHECK(successParse("{ 7 8 mul pop }")); } BOOST_AUTO_TEST_CASE(vardecl) @@ -162,37 +163,43 @@ BOOST_AUTO_TEST_CASE(vardecl) BOOST_AUTO_TEST_CASE(assignment) { - BOOST_CHECK(successParse("{ 7 8 add =: x }")); + BOOST_CHECK(successParse("{ let x := 2 7 8 add =: x }")); } BOOST_AUTO_TEST_CASE(label) { - BOOST_CHECK(successParse("{ 7 abc: 8 eq abc jump }")); + BOOST_CHECK(successParse("{ 7 abc: 8 eq abc jump pop }")); } BOOST_AUTO_TEST_CASE(label_complex) { - BOOST_CHECK(successParse("{ 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) }")); + BOOST_CHECK(successParse("{ 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) pop }")); } BOOST_AUTO_TEST_CASE(functional) { - BOOST_CHECK(successParse("{ add(7, mul(6, x)) add mul(7, 8) }")); + BOOST_CHECK(successParse("{ let x := 2 add(7, mul(6, x)) mul(7, 8) add =: x }")); } BOOST_AUTO_TEST_CASE(functional_assignment) { - BOOST_CHECK(successParse("{ x := 7 }")); + BOOST_CHECK(successParse("{ let x := 2 x := 7 }")); } BOOST_AUTO_TEST_CASE(functional_assignment_complex) { - BOOST_CHECK(successParse("{ x := add(7, mul(6, x)) add mul(7, 8) }")); + BOOST_CHECK(successParse("{ let x := 2 x := add(7, mul(6, x)) mul(7, 8) add }")); } BOOST_AUTO_TEST_CASE(vardecl_complex) { - BOOST_CHECK(successParse("{ let x := add(7, mul(6, x)) add mul(7, 8) }")); + BOOST_CHECK(successParse("{ let y := 2 let x := add(7, mul(6, y)) add mul(7, 8) }")); +} + +BOOST_AUTO_TEST_CASE(variable_use_before_decl) +{ + CHECK_PARSE_ERROR("{ x := 2 let x := 3 }", DeclarationError, "Variable x used before it was declared."); + CHECK_PARSE_ERROR("{ let x := mul(2, x) }", DeclarationError, "Variable x used before it was declared."); } BOOST_AUTO_TEST_CASE(blocks) @@ -202,17 +209,38 @@ BOOST_AUTO_TEST_CASE(blocks) BOOST_AUTO_TEST_CASE(function_definitions) { - BOOST_CHECK(successParse("{ function f() { } function g(a) -> (x) { } }")); + BOOST_CHECK(successParse("{ function f() { } function g(a) -> x { } }")); } BOOST_AUTO_TEST_CASE(function_definitions_multiple_args) { - BOOST_CHECK(successParse("{ function f(a, d) { } function g(a, d) -> (x, y) { } }")); + BOOST_CHECK(successParse("{ function f(a, d) { } function g(a, d) -> x, y { } }")); } BOOST_AUTO_TEST_CASE(function_calls) { - BOOST_CHECK(successParse("{ g(1, 2, f(mul(2, 3))) x() }")); + BOOST_CHECK(successParse("{ function f(a) -> b {} function g(a, b, c) {} function x() { g(1, 2, f(mul(2, 3))) x() } }")); +} + +BOOST_AUTO_TEST_CASE(opcode_for_functions) +{ + CHECK_PARSE_ERROR("{ function gas() { } }", ParserError, "Cannot use instruction names for identifier names."); +} + +BOOST_AUTO_TEST_CASE(opcode_for_function_args) +{ + CHECK_PARSE_ERROR("{ function f(gas) { } }", ParserError, "Cannot use instruction names for identifier names."); + CHECK_PARSE_ERROR("{ function f() -> gas { } }", ParserError, "Cannot use instruction names for identifier names."); +} + +BOOST_AUTO_TEST_CASE(name_clashes) +{ + CHECK_PARSE_ERROR("{ let g := 2 function g() { } }", DeclarationError, "Function name g already taken in this scope"); +} + +BOOST_AUTO_TEST_CASE(variable_access_cross_functions) +{ + CHECK_PARSE_ERROR("{ let x := 2 function g() { x pop } }", DeclarationError, "Identifier not found."); } BOOST_AUTO_TEST_SUITE_END() @@ -226,7 +254,7 @@ BOOST_AUTO_TEST_CASE(print_smoke) BOOST_AUTO_TEST_CASE(print_instructions) { - parsePrintCompare("{\n 7\n 8\n mul\n dup10\n add\n}"); + parsePrintCompare("{\n 7\n 8\n mul\n dup10\n add\n pop\n}"); } BOOST_AUTO_TEST_CASE(print_subblock) @@ -236,7 +264,7 @@ BOOST_AUTO_TEST_CASE(print_subblock) BOOST_AUTO_TEST_CASE(print_functional) { - parsePrintCompare("{\n mul(sload(0x12), 7)\n}"); + parsePrintCompare("{\n let x := mul(sload(0x12), 7)\n}"); } BOOST_AUTO_TEST_CASE(print_label) @@ -251,13 +279,13 @@ BOOST_AUTO_TEST_CASE(print_assignments) BOOST_AUTO_TEST_CASE(print_string_literals) { - parsePrintCompare("{\n \"\\n'\\xab\\x95\\\"\"\n}"); + parsePrintCompare("{\n \"\\n'\\xab\\x95\\\"\"\n pop\n}"); } BOOST_AUTO_TEST_CASE(print_string_literal_unicode) { - string source = "{ \"\\u1bac\" }"; - string parsed = "{\n \"\\xe1\\xae\\xac\"\n}"; + string source = "{ let x := \"\\u1bac\" }"; + string parsed = "{\n let x := \"\\xe1\\xae\\xac\"\n}"; assembly::InlineAssemblyStack stack; BOOST_REQUIRE(stack.parse(std::make_shared<Scanner>(CharStream(source)))); BOOST_REQUIRE(stack.errors().empty()); @@ -267,12 +295,26 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode) BOOST_AUTO_TEST_CASE(function_definitions_multiple_args) { - parsePrintCompare("{\n function f(a, d)\n {\n mstore(a, d)\n }\n function g(a, d) -> (x, y)\n {\n }\n}"); + parsePrintCompare("{\n function f(a, d)\n {\n mstore(a, d)\n }\n function g(a, d) -> x, y\n {\n }\n}"); } BOOST_AUTO_TEST_CASE(function_calls) { - parsePrintCompare("{\n g(1, mul(2, x), f(mul(2, 3)))\n x()\n}"); + string source = R"({ + function y() + { + } + function f(a) -> b + { + } + function g(a, b, c) + { + } + g(1, mul(2, address), f(mul(2, caller))) + y() +})"; + boost::replace_all(source, "\t", " "); + parsePrintCompare(source); } BOOST_AUTO_TEST_SUITE_END() @@ -291,27 +333,32 @@ BOOST_AUTO_TEST_CASE(oversize_string_literals) BOOST_AUTO_TEST_CASE(assignment_after_tag) { - BOOST_CHECK(successParse("{ let x := 1 { tag: =: x } }")); + BOOST_CHECK(successParse("{ let x := 1 { 7 tag: =: x } }")); } BOOST_AUTO_TEST_CASE(magic_variables) { - CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found or not unique"); - CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found or not unique"); - BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover }")); + CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found"); + CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found"); + BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover pop }")); +} + +BOOST_AUTO_TEST_CASE(stack_variables) +{ + BOOST_CHECK(successAssemble("{ let y := 3 { 2 { let x := y } pop} }")); } BOOST_AUTO_TEST_CASE(imbalanced_stack) { BOOST_CHECK(successAssemble("{ 1 2 mul pop }", false)); - CHECK_ASSEMBLE_ERROR("{ 1 }", Warning, "Inline assembly block is not balanced. It leaves"); - CHECK_ASSEMBLE_ERROR("{ pop }", Warning, "Inline assembly block is not balanced. It takes"); + CHECK_ASSEMBLE_ERROR("{ 1 }", DeclarationError, "Unbalanced stack at the end of a block: 1 surplus item(s)."); + CHECK_ASSEMBLE_ERROR("{ pop }", DeclarationError, "Unbalanced stack at the end of a block: 1 missing item(s)."); BOOST_CHECK(successAssemble("{ let x := 4 7 add }", false)); } BOOST_AUTO_TEST_CASE(error_tag) { - BOOST_CHECK(successAssemble("{ invalidJumpLabel }")); + BOOST_CHECK(successAssemble("{ jump(invalidJumpLabel) }")); } BOOST_AUTO_TEST_CASE(designated_invalid_instruction) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 8dd5042a..f2f4b8b0 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7426,18 +7426,52 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_access) uint16 x; uint16 public y; uint public z; - function f() { - // we know that z is aligned because it is too large, so we just discard its - // intra-slot offset value - assembly { 7 z pop sstore } + function f() returns (bool) { + uint off1; + uint off2; + assembly { + sstore(z_slot, 7) + off1 := z_offset + off2 := y_offset + } + assert(off1 == 0); + assert(off2 == 2); + return true; } } )"; compileAndRun(sourceCode, 0, "C"); - BOOST_CHECK(callContractFunction("f()") == encodeArgs()); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(true)); BOOST_CHECK(callContractFunction("z()") == encodeArgs(u256(7))); } +BOOST_AUTO_TEST_CASE(inline_assembly_storage_access_via_pointer) +{ + char const* sourceCode = R"( + contract C { + struct Data { uint contents; } + uint public separator; + Data public a; + uint public separator2; + function f() returns (bool) { + Data x = a; + uint off; + assembly { + sstore(x_slot, 7) + off := x_offset + } + assert(off == 0); + return true; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(true)); + BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("separator()") == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("separator2()") == encodeArgs(u256(0))); +} + BOOST_AUTO_TEST_CASE(inline_assembly_jumps) { char const* sourceCode = R"( @@ -7474,6 +7508,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_access) assembly { _x jump(g) + pop } } } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index b98c3706..f88f600a 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4997,7 +4997,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_positive_stack) } } )"; - CHECK_WARNING(text, "Inline assembly block is not balanced"); + CHECK_ERROR(text, DeclarationError, "Unbalanced stack at the end of a block: 1 surplus item(s)."); } BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack) @@ -5011,7 +5011,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack) } } )"; - CHECK_WARNING(text, "Inline assembly block is not balanced"); + CHECK_ERROR(text, DeclarationError, "Unbalanced stack at the end of a block: 1 missing item(s)."); } BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load) @@ -5024,7 +5024,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load) } } )"; - CHECK_WARNING(text, "Inline assembly block is not balanced"); + CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,"); } BOOST_AUTO_TEST_CASE(inline_assembly_in_modifier) @@ -5053,12 +5053,11 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage) function f() { assembly { x := 2 - pop } } } )"; - CHECK_ERROR(text, DeclarationError, "not found, not unique or not lvalue."); + CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,"); } BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers) @@ -5069,7 +5068,6 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers) modifier m { assembly { x := 2 - pop } _; } @@ -5077,7 +5075,37 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers) } } )"; - CHECK_ERROR(text, DeclarationError, ""); + CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,"); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_constant_assign) +{ + char const* text = R"( + contract test { + uint constant x = 1; + function f() { + assembly { + x := 2 + } + } + } + )"; + CHECK_ERROR(text, TypeError, "Constant variables not supported by inline assembly"); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_constant_access) +{ + char const* text = R"( + contract test { + uint constant x = 1; + function f() { + assembly { + let y := x + } + } + } + )"; + CHECK_ERROR(text, TypeError, "Constant variables not supported by inline assembly"); } BOOST_AUTO_TEST_CASE(invalid_mobile_type) |