diff options
30 files changed, 446 insertions, 102 deletions
diff --git a/Changelog.md b/Changelog.md index bfaeeb27..1c279dfb 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,15 +1,17 @@ ### 0.4.20 (unreleased) Features: + * Commandline interface: Support strict mode of assembly with the ``--strict--assembly`` switch. * Limit the number of warnings raised for creating abstract contracts. * Inline Assembly: Issue warning for using jump labels (already existed for jump instructions). + * Inline Assembly: Support some restricted tokens (return, byte, address) as identifiers in Julia mode. * SMT Checker: If-else branch conditions are taken into account in the SMT encoding of the program variables. Bugfixes: * Parser: Disallow event declarations with no parameter list. * Standard JSON: Populate the ``sourceLocation`` field in the error list. - * Standard JSON: Properly support file names containing a colon (such as URLs). + * Standard JSON: Properly support contract and library file names containing a colon (such as URLs). * Type Checker: Suggest the experimental ABI encoder if using ``struct``s as function parameters (instead of an internal compiler error). * Type Checker: Improve error message for wrong struct initialization. diff --git a/docs/frequently-asked-questions.rst b/docs/frequently-asked-questions.rst index 5343b848..7c934041 100644 --- a/docs/frequently-asked-questions.rst +++ b/docs/frequently-asked-questions.rst @@ -433,14 +433,14 @@ What happens to a ``struct``'s mapping when copying over a ``struct``? This is a very interesting question. Suppose that we have a contract field set up like such:: - struct user { + struct User { mapping(string => string) comments; } function somefunction public { - user user1; + User user1; user1.comments["Hello"] = "World"; - user user2 = user1; + User user2 = user1; } In this case, the mapping of the struct being copied over into the userList is ignored as there is no "list of mapped keys". diff --git a/docs/index.rst b/docs/index.rst index 3c617d36..0a49681e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -70,7 +70,7 @@ Available Solidity Integrations Configurable Solidty linter for Atom using Solium as a base. * `Solium <https://github.com/duaraghav8/Solium/>`_ - A commandline linter for Solidity which strictly follows the rules prescribed by the `Solidity Style Guide <http://solidity.readthedocs.io/en/latest/style-guide.html>`_. + Linter to identify and fix style and security issues in Solidity. * `Solhint <https://github.com/protofire/solhint>`_ Solidity linter that provides security, style guide and best practice rules for smart contract validation. diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index b660cf02..8f30f199 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -2,9 +2,9 @@ .. _installing-solidity: -################### -Installing Solidity -################### +################################ +Installing the Solidity Compiler +################################ Versioning ========== @@ -18,30 +18,38 @@ will use the latest release. Remix ===== -If you just want to try Solidity for small contracts, you -can try `Remix <https://remix.ethereum.org/>`_ -which does not need any installation. If you want to use it -without connection to the Internet, you can go to -https://github.com/ethereum/browser-solidity/tree/gh-pages and -download the .ZIP file as explained on that page. +*We recommend Remix for small contracts and for quickly learning Solidity.* + +`Access Remix online <https://remix.ethereum.org/>`_, you don't need to install anything. +If you want to use it without connection to the Internet, go to +https://github.com/ethereum/browser-solidity/tree/gh-pages and download the .ZIP file as +explained on that page. + +Further options on this page detail installing commandline Solidity compiler software +on your computer. Choose a commandline compiler if you are working on a larger contract +or if you require more compilation options. npm / Node.js ============= -This is probably the most portable and most convenient way to install Solidity locally. +Use `npm` for a convenient and portable way to install `solcjs`, a Solidity compiler. The +`solcjs` program has less features than all options further down this page. Our +`Using the compiler <using-the-compiler.html>` documentation assumes you are using +the full-featured compiler, `solc`. So if you install `solcjs` from `npm` then you will +stop reading the documentation here and then continue to <https://github.com/ethereum/solc-js>, -A platform-independent JavaScript library is provided by compiling the C++ source -into JavaScript using Emscripten. It can be used in projects directly (such as Remix). +Note: The `solc-js <https://github.com/ethereum/solc-js>` project is derived from the C++ +`solc` by using Emscripten. `solc-js` can be used in JavaScript projects directly (such as Remix). Please refer to the `solc-js <https://github.com/ethereum/solc-js>`_ repository for instructions. -It also contains a commandline tool called `solcjs`, which can be installed via npm: - .. code:: bash npm install -g solc .. note:: + The commandline is named `solcjs`. + The comandline options of `solcjs` are not compatible with `solc` and tools (such as `geth`) expecting the behaviour of `solc` will not work with `solcjs`. @@ -63,7 +71,7 @@ output directories. Binary Packages =============== -Binary packages of Solidity available at +Binary packages of Solidity are available at `solidity/releases <https://github.com/ethereum/solidity/releases>`_. We also have PPAs for Ubuntu. For the latest stable version. diff --git a/docs/style-guide.rst b/docs/style-guide.rst index 66fecff8..4c0d44f0 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -704,6 +704,12 @@ Contract and Library Names Contracts and libraries should be named using the CapWords style. Examples: ``SimpleToken``, ``SmartBank``, ``CertificateHashRepository``, ``Player``. +Struct Names +========================== + +Structs should be named using the CapWords style. Examples: ``MyCoin``, ``Position``, ``PositionXY``. + + Event Names =========== @@ -713,7 +719,7 @@ Events should be named using the CapWords style. Examples: ``Deposit``, ``Transf Function Names ============== -Functions should use mixedCase. Examples: ``getBalance``, ``transfer``, ``verifyOwner``, ``addMember``, ``changeOwner``. +Functions other than constructors should use mixedCase. Examples: ``getBalance``, ``transfer``, ``verifyOwner``, ``addMember``, ``changeOwner``. Function Argument Names diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 61a6ccda..4c2290c4 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -153,6 +153,31 @@ bool SemanticInformation::isDeterministic(AssemblyItem const& _item) } } +bool SemanticInformation::movable(Instruction _instruction) +{ + // These are not really functional. + if (isDupInstruction(_instruction) || isSwapInstruction(_instruction)) + return false; + InstructionInfo info = instructionInfo(_instruction); + if (info.sideEffects) + return false; + switch (_instruction) + { + case Instruction::KECCAK256: + case Instruction::BALANCE: + case Instruction::EXTCODESIZE: + case Instruction::RETURNDATASIZE: + case Instruction::SLOAD: + case Instruction::PC: + case Instruction::MSIZE: + case Instruction::GAS: + return false; + default: + return true; + } + return true; +} + bool SemanticInformation::invalidatesMemory(Instruction _instruction) { switch (_instruction) diff --git a/libevmasm/SemanticInformation.h b/libevmasm/SemanticInformation.h index e5ea7c18..83656252 100644 --- a/libevmasm/SemanticInformation.h +++ b/libevmasm/SemanticInformation.h @@ -49,6 +49,10 @@ struct SemanticInformation /// @returns false if the value put on the stack by _item depends on anything else than /// the information in the current block header, memory, storage or stack. static bool isDeterministic(AssemblyItem const& _item); + /// @returns true if the instruction can be moved or copied (together with its arguments) + /// without altering the semantics. This means it cannot depend on storage or memory, + /// cannot have any side-effects, but it can depend on a call-constant state of the blockchain. + static bool movable(solidity::Instruction _instruction); /// @returns true if the given instruction modifies memory. static bool invalidatesMemory(solidity::Instruction _instruction); /// @returns true if the given instruction modifies storage (even indirectly). diff --git a/libjulia/optimiser/ASTWalker.cpp b/libjulia/optimiser/ASTWalker.cpp index 499b4bf2..6386b29d 100644 --- a/libjulia/optimiser/ASTWalker.cpp +++ b/libjulia/optimiser/ASTWalker.cpp @@ -44,31 +44,31 @@ void ASTWalker::operator()(FunctionCall const& _funCall) void ASTWalker::operator()(ExpressionStatement const& _statement) { - boost::apply_visitor(*this, _statement.expression); + visit(_statement.expression); } void ASTWalker::operator()(Assignment const& _assignment) { for (auto const& name: _assignment.variableNames) (*this)(name); - boost::apply_visitor(*this, *_assignment.value); + visit(*_assignment.value); } void ASTWalker::operator()(VariableDeclaration const& _varDecl) { if (_varDecl.value) - boost::apply_visitor(*this, *_varDecl.value); + visit(*_varDecl.value); } void ASTWalker::operator()(If const& _if) { - boost::apply_visitor(*this, *_if.condition); + visit(*_if.condition); (*this)(_if.body); } void ASTWalker::operator()(Switch const& _switch) { - boost::apply_visitor(*this, *_switch.expression); + visit(*_switch.expression); for (auto const& _case: _switch.cases) { if (_case.value) @@ -85,7 +85,7 @@ void ASTWalker::operator()(FunctionDefinition const& _fun) void ASTWalker::operator()(ForLoop const& _for) { (*this)(_for.pre); - boost::apply_visitor(*this, *_for.condition); + visit(*_for.condition); (*this)(_for.post); (*this)(_for.body); } @@ -107,7 +107,7 @@ void ASTModifier::operator()(FunctionCall& _funCall) void ASTModifier::operator()(ExpressionStatement& _statement) { - boost::apply_visitor(*this, _statement.expression); + visit(_statement.expression); } void ASTModifier::operator()(Assignment& _assignment) diff --git a/libjulia/optimiser/ASTWalker.h b/libjulia/optimiser/ASTWalker.h index 4652a353..dbf8194b 100644 --- a/libjulia/optimiser/ASTWalker.h +++ b/libjulia/optimiser/ASTWalker.h @@ -58,12 +58,21 @@ public: virtual void operator()(ForLoop const&); virtual void operator()(Block const& _block); + virtual void visit(Statement const& _st) + { + boost::apply_visitor(*this, _st); + } + virtual void visit(Expression const& _e) + { + boost::apply_visitor(*this, _e); + } + protected: template <class T> void walkVector(T const& _statements) { for (auto const& st: _statements) - boost::apply_visitor(*this, st); + visit(st); } }; @@ -89,13 +98,6 @@ public: virtual void operator()(ForLoop&); virtual void operator()(Block& _block); -protected: - template <class T> - void walkVector(T&& _statements) - { - for (auto& st: _statements) - visit(st); - } virtual void visit(Statement& _st) { boost::apply_visitor(*this, _st); @@ -104,6 +106,14 @@ protected: { boost::apply_visitor(*this, _e); } + +protected: + template <class T> + void walkVector(T&& _statements) + { + for (auto& st: _statements) + visit(st); + } }; } diff --git a/libjulia/optimiser/Disambiguator.h b/libjulia/optimiser/Disambiguator.h index cc9488d5..18ffd157 100644 --- a/libjulia/optimiser/Disambiguator.h +++ b/libjulia/optimiser/Disambiguator.h @@ -35,7 +35,6 @@ namespace dev { namespace julia { -class EVMAssembly; /** * Creates a copy of a iulia AST replacing all identifiers by unique names. diff --git a/libjulia/optimiser/Semantics.cpp b/libjulia/optimiser/Semantics.cpp new file mode 100644 index 00000000..92728c46 --- /dev/null +++ b/libjulia/optimiser/Semantics.cpp @@ -0,0 +1,60 @@ +/*( + 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/>. +*/ +/** + * Specific AST walkers that collect semantical facts. + */ + +#include <libjulia/optimiser/Semantics.h> + +#include <libsolidity/inlineasm/AsmData.h> + +#include <libevmasm/SemanticInformation.h> + +#include <libdevcore/CommonData.h> + +using namespace std; +using namespace dev; +using namespace dev::julia; + +MovableChecker::MovableChecker(Expression const& _expression) +{ + visit(_expression); +} + +void MovableChecker::operator()(Identifier const& _identifier) +{ + ASTWalker::operator()(_identifier); + m_variableReferences.insert(_identifier.name); +} + +void MovableChecker::operator()(FunctionalInstruction const& _instr) +{ + if (!eth::SemanticInformation::movable(_instr.instruction)) + m_movable = false; + else + ASTWalker::operator()(_instr); +} + +void MovableChecker::operator()(FunctionCall const&) +{ + m_movable = false; +} + +void MovableChecker::visit(Statement const&) +{ + solAssert(false, "Movability for statement requested."); +} diff --git a/libjulia/optimiser/Semantics.h b/libjulia/optimiser/Semantics.h new file mode 100644 index 00000000..6df5f01a --- /dev/null +++ b/libjulia/optimiser/Semantics.h @@ -0,0 +1,62 @@ +/* + 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/>. +*/ +/** + * Specific AST walkers that collect semantical facts. + */ + +#pragma once + +#include <libjulia/optimiser/ASTWalker.h> + +#include <string> +#include <map> +#include <set> + +namespace dev +{ +namespace julia +{ + +/** + * Specific AST walker that determines whether an expression is movable. + */ +class MovableChecker: public ASTWalker +{ +public: + MovableChecker() = default; + explicit MovableChecker(Expression const& _expression); + + virtual void operator()(Identifier const& _identifier) override; + virtual void operator()(FunctionalInstruction const& _functionalInstruction) override; + virtual void operator()(FunctionCall const& _functionCall) override; + + /// Disallow visiting anything apart from Expressions (this throws). + virtual void visit(Statement const&) override; + using ASTWalker::visit; + + bool movable() const { return m_movable; } + std::set<std::string> const& referencedVariables() const { return m_variableReferences; } + +private: + /// Which variables the current expression references. + std::set<std::string> m_variableReferences; + /// Is the current expression movable or not. + bool m_movable = true; +}; + +} +} diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 9eee16af..540ffaf5 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -207,7 +207,7 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) // Will be re-generated later with correct information assembly::AsmAnalysisInfo analysisInfo; - assembly::AsmAnalyzer(analysisInfo, errorsIgnored, false, resolver).analyze(_inlineAssembly.operations()); + assembly::AsmAnalyzer(analysisInfo, errorsIgnored, assembly::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations()); return false; } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 75d71925..191f78e9 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -873,7 +873,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) assembly::AsmAnalyzer analyzer( *_inlineAssembly.annotation().analysisInfo, m_errorReporter, - false, + assembly::AsmFlavour::Loose, identifierAccess ); if (!analyzer.analyze(_inlineAssembly.operations())) diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index ab10d7dd..7a88475a 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -319,14 +319,19 @@ void CompilerContext::appendInlineAssembly( ErrorList errors; ErrorReporter errorReporter(errors); auto scanner = make_shared<Scanner>(CharStream(_assembly), "--CODEGEN--"); - auto parserResult = assembly::Parser(errorReporter).parse(scanner); + auto parserResult = assembly::Parser(errorReporter, assembly::AsmFlavour::Strict).parse(scanner); #ifdef SOL_OUTPUT_ASM cout << assembly::AsmPrinter()(*parserResult) << endl; #endif assembly::AsmAnalysisInfo analysisInfo; bool analyzerResult = false; if (parserResult) - analyzerResult = assembly::AsmAnalyzer(analysisInfo, errorReporter, false, identifierAccess.resolve).analyze(*parserResult); + analyzerResult = assembly::AsmAnalyzer( + analysisInfo, + errorReporter, + assembly::AsmFlavour::Strict, + identifierAccess.resolve + ).analyze(*parserResult); if (!parserResult || !errorReporter.errors().empty() || !analyzerResult) { string message = diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 7743fd3f..0e8b639c 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -187,8 +187,8 @@ public: CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; } CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; } - /// Appends inline assembly. @a _replacements are string-matching replacements that are performed - /// prior to parsing the inline assembly. + /// Appends inline assembly (strict mode). + /// @a _replacements are string-matching replacements that are performed prior to parsing the inline assembly. /// @param _localVariables assigns stack positions to variables with the last one being the stack top /// @param _system if true, this is a "system-level" assembly where all functions use named labels. void appendInlineAssembly( diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index 17b7cce0..2d6e58de 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -54,7 +54,7 @@ bool AsmAnalyzer::analyze(Block const& _block) bool AsmAnalyzer::operator()(Label const& _label) { - solAssert(!m_julia, ""); + solAssert(m_flavour == AsmFlavour::Loose, ""); m_info.stackHeightInfo[&_label] = m_stackHeight; warnOnInstructions(solidity::Instruction::JUMPDEST, _label.location); return true; @@ -62,7 +62,7 @@ bool AsmAnalyzer::operator()(Label const& _label) bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction) { - solAssert(!m_julia, ""); + solAssert(m_flavour == AsmFlavour::Loose, ""); auto const& info = instructionInfo(_instruction.instruction); m_stackHeight += info.ret - info.args; m_info.stackHeightInfo[&_instruction] = m_stackHeight; @@ -141,7 +141,7 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier) bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) { - solAssert(!m_julia, ""); + solAssert(m_flavour != AsmFlavour::IULIA, ""); bool success = true; for (auto const& arg: _instr.arguments | boost::adaptors::reversed) if (!expectExpression(arg)) @@ -157,17 +157,18 @@ bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) bool AsmAnalyzer::operator()(assembly::ExpressionStatement const& _statement) { -// size_t initialStackHeight = m_stackHeight; + size_t initialStackHeight = m_stackHeight; bool success = boost::apply_visitor(*this, _statement.expression); -// if (!expectDeposit(0, initialStackHeight, _statement.location)) -// success = false; + if (m_flavour != AsmFlavour::Loose) + if (!expectDeposit(0, initialStackHeight, _statement.location)) + success = false; m_info.stackHeightInfo[&_statement] = m_stackHeight; return success; } bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment) { - solAssert(!m_julia, ""); + solAssert(m_flavour == AsmFlavour::Loose, ""); bool success = checkAssignment(_assignment.variableName, size_t(-1)); m_info.stackHeightInfo[&_assignment] = m_stackHeight; return success; @@ -507,7 +508,7 @@ Scope& AsmAnalyzer::scope(Block const* _block) } void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location) { - if (!m_julia) + if (m_flavour != AsmFlavour::IULIA) return; if (!builtinTypes.count(type)) diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h index 00a33db3..7a81dbf8 100644 --- a/libsolidity/inlineasm/AsmAnalysis.h +++ b/libsolidity/inlineasm/AsmAnalysis.h @@ -54,9 +54,9 @@ public: explicit AsmAnalyzer( AsmAnalysisInfo& _analysisInfo, ErrorReporter& _errorReporter, - bool _julia = false, + AsmFlavour _flavour = AsmFlavour::Loose, julia::ExternalIdentifierAccess::Resolver const& _resolver = julia::ExternalIdentifierAccess::Resolver() - ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_julia(_julia) {} + ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_flavour(_flavour) {} bool analyze(assembly::Block const& _block); @@ -97,7 +97,7 @@ private: std::set<Scope::Variable const*> m_activeVariables; AsmAnalysisInfo& m_info; ErrorReporter& m_errorReporter; - bool m_julia = false; + AsmFlavour m_flavour = AsmFlavour::Loose; }; } diff --git a/libsolidity/inlineasm/AsmDataForward.h b/libsolidity/inlineasm/AsmDataForward.h index 317e257c..3a9600fe 100644 --- a/libsolidity/inlineasm/AsmDataForward.h +++ b/libsolidity/inlineasm/AsmDataForward.h @@ -53,6 +53,13 @@ struct TypedName; using Expression = boost::variant<FunctionalInstruction, FunctionCall, Identifier, Literal>; using Statement = boost::variant<ExpressionStatement, Instruction, Label, StackAssignment, Assignment, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>; +enum class AsmFlavour +{ + Loose, // no types, EVM instructions as function, jumps and direct stack manipulations + Strict, // no types, EVM instructions as functions, but no jumps and no direct stack manipulations + IULIA // same as Strict mode with types +}; + } } } diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 273e1d5c..306b07e6 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -103,14 +103,14 @@ assembly::Statement Parser::parseStatement() return parseForLoop(); case Token::Assign: { - if (m_julia) + if (m_flavour != AsmFlavour::Loose) break; assembly::StackAssignment assignment = createWithLocation<assembly::StackAssignment>(); advance(); expectToken(Token::Colon); assignment.variableName.location = location(); assignment.variableName.name = currentLiteral(); - if (!m_julia && instructions().count(assignment.variableName.name)) + if (instructions().count(assignment.variableName.name)) fatalParserError("Identifier expected, got instruction name."); assignment.location.end = endPosition(); expectToken(Token::Identifier); @@ -123,7 +123,7 @@ assembly::Statement Parser::parseStatement() // Simple instruction (might turn into functional), // literal, // identifier (might turn into label or functional assignment) - ElementaryOperation elementary(parseElementaryOperation(false)); + ElementaryOperation elementary(parseElementaryOperation()); switch (currentToken()) { case Token::LParen: @@ -145,7 +145,7 @@ assembly::Statement Parser::parseStatement() do { expectToken(Token::Comma); - elementary = parseElementaryOperation(false); + elementary = parseElementaryOperation(); if (elementary.type() != typeid(assembly::Identifier)) fatalParserError("Variable name expected in multiple assignemnt."); assignment.variableNames.emplace_back(boost::get<assembly::Identifier>(elementary)); @@ -170,7 +170,7 @@ assembly::Statement Parser::parseStatement() if (currentToken() == Token::Assign && peekNextToken() != Token::Colon) { assembly::Assignment assignment = createWithLocation<assembly::Assignment>(identifier.location); - if (!m_julia && instructions().count(identifier.name)) + if (m_flavour != AsmFlavour::IULIA && instructions().count(identifier.name)) fatalParserError("Cannot use instruction names for identifier names."); advance(); assignment.variableNames.emplace_back(identifier); @@ -181,7 +181,7 @@ assembly::Statement Parser::parseStatement() else { // label - if (m_julia) + if (m_flavour != AsmFlavour::Loose) fatalParserError("Labels are not supported."); Label label = createWithLocation<Label>(identifier.location); label.name = identifier.name; @@ -189,7 +189,7 @@ assembly::Statement Parser::parseStatement() } } default: - if (m_julia) + if (m_flavour != AsmFlavour::Loose) fatalParserError("Call or assignment expected."); break; } @@ -247,10 +247,29 @@ assembly::ForLoop Parser::parseForLoop() assembly::Expression Parser::parseExpression() { RecursionGuard recursionGuard(*this); - ElementaryOperation operation = parseElementaryOperation(true); + // In strict mode, this might parse a plain Instruction, but + // it will be converted to a FunctionalInstruction inside + // parseCall below. + ElementaryOperation operation = parseElementaryOperation(); if (operation.type() == typeid(Instruction)) { Instruction const& instr = boost::get<Instruction>(operation); + // Disallow instructions returning multiple values (and DUP/SWAP) as expression. + if ( + instructionInfo(instr.instruction).ret != 1 || + isDupInstruction(instr.instruction) || + isSwapInstruction(instr.instruction) + ) + fatalParserError( + "Instruction \"" + + instructionNames().at(instr.instruction) + + "\" not allowed in this context." + ); + if (m_flavour != AsmFlavour::Loose && currentToken() != Token::LParen) + fatalParserError( + "Non-functional instructions are not allowed in this context." + ); + // Enforce functional notation for instructions requiring multiple arguments. int args = instructionInfo(instr.instruction).args; if (args > 0 && currentToken() != Token::LParen) fatalParserError(string( @@ -265,6 +284,8 @@ assembly::Expression Parser::parseExpression() return parseCall(std::move(operation)); else if (operation.type() == typeid(Instruction)) { + // Instructions not taking arguments are allowed as expressions. + solAssert(m_flavour == AsmFlavour::Loose, ""); Instruction& instr = boost::get<Instruction>(operation); return FunctionalInstruction{std::move(instr.location), instr.instruction, {}}; } @@ -317,7 +338,7 @@ std::map<dev::solidity::Instruction, string> const& Parser::instructionNames() return s_instructionNames; } -Parser::ElementaryOperation Parser::parseElementaryOperation(bool _onlySinglePusher) +Parser::ElementaryOperation Parser::parseElementaryOperation() { RecursionGuard recursionGuard(*this); ElementaryOperation ret; @@ -338,15 +359,9 @@ Parser::ElementaryOperation Parser::parseElementaryOperation(bool _onlySinglePus else literal = currentLiteral(); // first search the set of instructions. - if (!m_julia && instructions().count(literal)) + if (m_flavour != AsmFlavour::IULIA && instructions().count(literal)) { dev::solidity::Instruction const& instr = instructions().at(literal); - if (_onlySinglePusher) - { - InstructionInfo info = dev::solidity::instructionInfo(instr); - if (info.ret != 1) - fatalParserError("Instruction \"" + literal + "\" not allowed in this context."); - } ret = Instruction{location(), instr}; } else @@ -385,7 +400,7 @@ Parser::ElementaryOperation Parser::parseElementaryOperation(bool _onlySinglePus "" }; advance(); - if (m_julia) + if (m_flavour == AsmFlavour::IULIA) { expectToken(Token::Colon); literal.location.end = endPosition(); @@ -398,7 +413,7 @@ Parser::ElementaryOperation Parser::parseElementaryOperation(bool _onlySinglePus } default: fatalParserError( - m_julia ? + m_flavour == AsmFlavour::IULIA ? "Literal or identifier expected." : "Literal, identifier or instruction expected." ); @@ -468,7 +483,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) RecursionGuard recursionGuard(*this); if (_initialOp.type() == typeid(Instruction)) { - solAssert(!m_julia, "Instructions are invalid in JULIA"); + solAssert(m_flavour != AsmFlavour::IULIA, "Instructions are invalid in JULIA"); Instruction& instruction = boost::get<Instruction>(_initialOp); FunctionalInstruction ret; ret.instruction = instruction.instruction; @@ -539,7 +554,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) } else fatalParserError( - m_julia ? + m_flavour == AsmFlavour::IULIA ? "Function name expected." : "Assembly instruction or function name required in front of \"(\")" ); @@ -552,7 +567,7 @@ TypedName Parser::parseTypedName() RecursionGuard recursionGuard(*this); TypedName typedName = createWithLocation<TypedName>(); typedName.name = expectAsmIdentifier(); - if (m_julia) + if (m_flavour == AsmFlavour::IULIA) { expectToken(Token::Colon); typedName.location.end = endPosition(); @@ -564,12 +579,18 @@ TypedName Parser::parseTypedName() string Parser::expectAsmIdentifier() { string name = currentLiteral(); - if (m_julia) + if (m_flavour == AsmFlavour::IULIA) { - if (currentToken() == Token::Bool) + switch (currentToken()) { + case Token::Return: + case Token::Byte: + case Token::Address: + case Token::Bool: advance(); return name; + default: + break; } } else if (instructions().count(name)) diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index 44889a13..015aeef3 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -37,7 +37,8 @@ namespace assembly class Parser: public ParserBase { public: - explicit Parser(ErrorReporter& _errorReporter, bool _julia = false): ParserBase(_errorReporter), m_julia(_julia) {} + explicit Parser(ErrorReporter& _errorReporter, AsmFlavour _flavour = AsmFlavour::Loose): + ParserBase(_errorReporter), m_flavour(_flavour) {} /// Parses an inline assembly block starting with `{` and ending with `}`. /// @returns an empty shared pointer on error. @@ -70,7 +71,10 @@ protected: assembly::Expression parseExpression(); static std::map<std::string, dev::solidity::Instruction> const& instructions(); static std::map<dev::solidity::Instruction, std::string> const& instructionNames(); - ElementaryOperation parseElementaryOperation(bool _onlySinglePusher = false); + /// Parses an elementary operation, i.e. a literal, identifier or instruction. + /// This will parse instructions even in strict mode as part of the full parser + /// for FunctionalInstruction. + ElementaryOperation parseElementaryOperation(); VariableDeclaration parseVariableDeclaration(); FunctionDefinition parseFunctionDefinition(); assembly::Expression parseCall(ElementaryOperation&& _initialOp); @@ -80,7 +84,7 @@ protected: static bool isValidNumberLiteral(std::string const& _literal); private: - bool m_julia = false; + AsmFlavour m_flavour = AsmFlavour::Loose; }; } diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp index 504ad92c..1b4bd270 100644 --- a/libsolidity/interface/AssemblyStack.cpp +++ b/libsolidity/interface/AssemblyStack.cpp @@ -38,6 +38,25 @@ using namespace std; using namespace dev; using namespace dev::solidity; +namespace +{ +assembly::AsmFlavour languageToAsmFlavour(AssemblyStack::Language _language) +{ + switch (_language) + { + case AssemblyStack::Language::Assembly: + return assembly::AsmFlavour::Loose; + case AssemblyStack::Language::StrictAssembly: + return assembly::AsmFlavour::Strict; + case AssemblyStack::Language::JULIA: + return assembly::AsmFlavour::IULIA; + } + solAssert(false, ""); + return assembly::AsmFlavour::IULIA; +} + +} + Scanner const& AssemblyStack::scanner() const { @@ -50,7 +69,7 @@ bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string m_errors.clear(); m_analysisSuccessful = false; m_scanner = make_shared<Scanner>(CharStream(_source), _sourceName); - m_parserResult = assembly::Parser(m_errorReporter, m_language == Language::JULIA).parse(m_scanner); + m_parserResult = assembly::Parser(m_errorReporter, languageToAsmFlavour(m_language)).parse(m_scanner); if (!m_errorReporter.errors().empty()) return false; solAssert(m_parserResult, ""); @@ -72,7 +91,7 @@ bool AssemblyStack::analyze(assembly::Block const& _block, Scanner const* _scann bool AssemblyStack::analyzeParsed() { m_analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); - assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, m_language == Language::JULIA); + assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, languageToAsmFlavour(m_language)); m_analysisSuccessful = analyzer.analyze(*m_parserResult); return m_analysisSuccessful; } diff --git a/libsolidity/interface/AssemblyStack.h b/libsolidity/interface/AssemblyStack.h index 2ae596ed..6ae7e8d1 100644 --- a/libsolidity/interface/AssemblyStack.h +++ b/libsolidity/interface/AssemblyStack.h @@ -51,7 +51,7 @@ struct MachineAssemblyObject class AssemblyStack { public: - enum class Language { JULIA, Assembly }; + enum class Language { JULIA, Assembly, StrictAssembly }; enum class Machine { EVM, EVM15, eWasm }; explicit AssemblyStack(Language _language = Language::Assembly): diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 7aa971c6..04f5bd25 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -193,7 +193,7 @@ Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkRefere for (auto const& ref: linkReferences) { string const& fullname = ref.second; - size_t colon = fullname.find(':'); + size_t colon = fullname.rfind(':'); solAssert(colon != string::npos, ""); string file = fullname.substr(0, colon); string name = fullname.substr(colon + 1); diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index b6bae690..a8966f96 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -137,7 +137,7 @@ case $(uname -s) in # All our dependencies can be found in the Arch Linux official repositories. # See https://wiki.archlinux.org/index.php/Official_repositories # Also adding ethereum-git to allow for testing with the `eth` client - sudo pacman -Sy \ + sudo pacman -Syu \ base-devel \ boost \ cmake \ diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 9e2cb77a..adcfee9c 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -97,6 +97,7 @@ static string const g_strJulia = "julia"; static string const g_strLicense = "license"; static string const g_strLibraries = "libraries"; static string const g_strLink = "link"; +static string const g_strMachine = "machine"; static string const g_strMetadata = "metadata"; static string const g_strMetadataLiteral = "metadata-literal"; static string const g_strNatspecDev = "devdoc"; @@ -112,6 +113,7 @@ static string const g_strSourceList = "sourceList"; static string const g_strSrcMap = "srcmap"; static string const g_strSrcMapRuntime = "srcmap-runtime"; static string const g_strStandardJSON = "standard-json"; +static string const g_strStrictAssembly = "strict-assembly"; static string const g_strPrettyJson = "pretty-json"; static string const g_strVersion = "version"; @@ -134,10 +136,10 @@ static string const g_argFormal = g_strFormal; static string const g_argGas = g_strGas; static string const g_argHelp = g_strHelp; static string const g_argInputFile = g_strInputFile; -static string const g_argJulia = "julia"; +static string const g_argJulia = g_strJulia; static string const g_argLibraries = g_strLibraries; static string const g_argLink = g_strLink; -static string const g_argMachine = "machine"; +static string const g_argMachine = g_strMachine; static string const g_argMetadata = g_strMetadata; static string const g_argMetadataLiteral = g_strMetadataLiteral; static string const g_argNatspecDev = g_strNatspecDev; @@ -148,6 +150,7 @@ static string const g_argOptimizeRuns = g_strOptimizeRuns; static string const g_argOutputDir = g_strOutputDir; static string const g_argSignatureHashes = g_strSignatureHashes; static string const g_argStandardJSON = g_strStandardJSON; +static string const g_argStrictAssembly = g_strStrictAssembly; static string const g_argVersion = g_strVersion; static string const g_stdinFileName = g_stdinFileNameStr; @@ -575,6 +578,10 @@ Allowed options)", "Switch to JULIA mode, ignoring all options except --machine and assumes input is JULIA." ) ( + g_argStrictAssembly.c_str(), + "Switch to strict assembly mode, ignoring all options except --machine and assumes input is strict assembly." + ) + ( g_argMachine.c_str(), po::value<string>()->value_name(boost::join(g_machineArgs, ",")), "Target machine in assembly or JULIA mode." @@ -737,13 +744,13 @@ bool CommandLineInterface::processInput() if (!parseLibraryOption(library)) return false; - if (m_args.count(g_argAssemble) || m_args.count(g_argJulia)) + if (m_args.count(g_argAssemble) || m_args.count(g_argStrictAssembly) || m_args.count(g_argJulia)) { // switch to assembly mode m_onlyAssemble = true; using Input = AssemblyStack::Language; using Machine = AssemblyStack::Machine; - Input inputLanguage = m_args.count(g_argJulia) ? Input::JULIA : Input::Assembly; + Input inputLanguage = m_args.count(g_argJulia) ? Input::JULIA : (m_args.count(g_argStrictAssembly) ? Input::StrictAssembly : Input::Assembly); Machine targetMachine = Machine::EVM; if (m_args.count(g_argMachine)) { diff --git a/test/libjulia/Common.cpp b/test/libjulia/Common.cpp index da1538f3..e1ab8215 100644 --- a/test/libjulia/Common.cpp +++ b/test/libjulia/Common.cpp @@ -52,15 +52,16 @@ void dev::julia::test::printErrors(ErrorList const& _errors, Scanner const& _sca pair<shared_ptr<Block>, shared_ptr<assembly::AsmAnalysisInfo>> dev::julia::test::parse(string const& _source, bool _julia) { + auto flavour = _julia ? assembly::AsmFlavour::IULIA : assembly::AsmFlavour::Strict; ErrorList errors; ErrorReporter errorReporter(errors); auto scanner = make_shared<Scanner>(CharStream(_source), ""); - auto parserResult = assembly::Parser(errorReporter, _julia).parse(scanner); + auto parserResult = assembly::Parser(errorReporter, flavour).parse(scanner); if (parserResult) { BOOST_REQUIRE(errorReporter.errors().empty()); auto analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); - assembly::AsmAnalyzer analyzer(*analysisInfo, errorReporter, _julia); + assembly::AsmAnalyzer analyzer(*analysisInfo, errorReporter, flavour); if (analyzer.analyze(*parserResult)) { BOOST_REQUIRE(errorReporter.errors().empty()); diff --git a/test/libjulia/Parser.cpp b/test/libjulia/Parser.cpp index 9aa325a4..a8a41b3c 100644 --- a/test/libjulia/Parser.cpp +++ b/test/libjulia/Parser.cpp @@ -52,11 +52,11 @@ bool parse(string const& _source, ErrorReporter& errorReporter) try { auto scanner = make_shared<Scanner>(CharStream(_source)); - auto parserResult = assembly::Parser(errorReporter, true).parse(scanner); + auto parserResult = assembly::Parser(errorReporter, assembly::AsmFlavour::IULIA).parse(scanner); if (parserResult) { assembly::AsmAnalysisInfo analysisInfo; - return (assembly::AsmAnalyzer(analysisInfo, errorReporter, true)).analyze(*parserResult); + return (assembly::AsmAnalyzer(analysisInfo, errorReporter, assembly::AsmFlavour::IULIA)).analyze(*parserResult); } } catch (FatalError const&) @@ -196,6 +196,14 @@ BOOST_AUTO_TEST_CASE(empty_call) CHECK_ERROR("{ () }", ParserError, "Literal or identifier expected."); } +BOOST_AUTO_TEST_CASE(tokens_as_identifers) +{ + BOOST_CHECK(successParse("{ let return:u256 := 1:u256 }")); + BOOST_CHECK(successParse("{ let byte:u256 := 1:u256 }")); + BOOST_CHECK(successParse("{ let address:u256 := 1:u256 }")); + BOOST_CHECK(successParse("{ let bool:u256 := 1:u256 }")); +} + BOOST_AUTO_TEST_CASE(lacking_types) { CHECK_ERROR("{ let x := 1:u256 }", ParserError, "Expected token Identifier got 'Assign'"); diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 94e02b8f..b09eb261 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -51,10 +51,11 @@ boost::optional<Error> parseAndReturnFirstError( string const& _source, bool _assemble = false, bool _allowWarnings = true, + AssemblyStack::Language _language = AssemblyStack::Language::Assembly, AssemblyStack::Machine _machine = AssemblyStack::Machine::EVM ) { - AssemblyStack stack; + AssemblyStack stack(_language); bool success = false; try { @@ -87,22 +88,29 @@ bool successParse( string const& _source, bool _assemble = false, bool _allowWarnings = true, + AssemblyStack::Language _language = AssemblyStack::Language::Assembly, AssemblyStack::Machine _machine = AssemblyStack::Machine::EVM ) { - return !parseAndReturnFirstError(_source, _assemble, _allowWarnings, _machine); + return !parseAndReturnFirstError(_source, _assemble, _allowWarnings, _language, _machine); } -bool successAssemble(string const& _source, bool _allowWarnings = true) +bool successAssemble(string const& _source, bool _allowWarnings = true, AssemblyStack::Language _language = AssemblyStack::Language::Assembly) { - return successParse(_source, true, _allowWarnings, AssemblyStack::Machine::EVM) && - successParse(_source, true, _allowWarnings, AssemblyStack::Machine::EVM15); + return + successParse(_source, true, _allowWarnings, _language, AssemblyStack::Machine::EVM) && + successParse(_source, true, _allowWarnings, _language, AssemblyStack::Machine::EVM15); } -Error expectError(std::string const& _source, bool _assemble, bool _allowWarnings = false) +Error expectError( + std::string const& _source, + bool _assemble, + bool _allowWarnings = false, + AssemblyStack::Language _language = AssemblyStack::Language::Assembly +) { - auto error = parseAndReturnFirstError(_source, _assemble, _allowWarnings); + auto error = parseAndReturnFirstError(_source, _assemble, _allowWarnings, _language); BOOST_REQUIRE(error); return *error; } @@ -120,14 +128,17 @@ void parsePrintCompare(string const& _source, bool _canWarn = false) } -#define CHECK_ERROR(text, assemble, typ, substring, warnings) \ +#define CHECK_ERROR_LANG(text, assemble, typ, substring, warnings, language) \ do \ { \ - Error err = expectError((text), (assemble), warnings); \ + Error err = expectError((text), (assemble), warnings, (language)); \ BOOST_CHECK(err.type() == (Error::Type::typ)); \ BOOST_CHECK(searchErrorMessage(err, (substring))); \ } while(0) +#define CHECK_ERROR(text, assemble, typ, substring, warnings) \ +CHECK_ERROR_LANG(text, assemble, typ, substring, warnings, AssemblyStack::Language::Assembly) + #define CHECK_PARSE_ERROR(text, type, substring) \ CHECK_ERROR(text, false, type, substring, false) @@ -137,6 +148,14 @@ CHECK_ERROR(text, false, type, substring, false) #define CHECK_ASSEMBLE_ERROR(text, type, substring) \ CHECK_ERROR(text, true, type, substring, false) +#define CHECK_STRICT_ERROR(text, type, substring) \ +CHECK_ERROR_LANG(text, false, type, substring, false, AssemblyStack::Language::StrictAssembly) + +#define CHECK_STRICT_WARNING(text, type, substring) \ +CHECK_ERROR(text, false, type, substring, false, AssemblyStack::Language::StrictAssembly) + +#define SUCCESS_STRICT(text) \ +do { successParse((text), false, false, AssemblyStack::Language::StrictAssembly); } while (false) BOOST_AUTO_TEST_SUITE(SolidityInlineAssembly) @@ -455,6 +474,47 @@ BOOST_AUTO_TEST_CASE(multiple_assignment) BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE(LooseStrictMode) + +BOOST_AUTO_TEST_CASE(no_opcodes_in_strict) +{ + BOOST_CHECK(successParse("{ pop(callvalue) }")); + BOOST_CHECK(successParse("{ callvalue pop }")); + CHECK_STRICT_ERROR("{ pop(callvalue) }", ParserError, "Non-functional instructions are not allowed in this context."); + CHECK_STRICT_ERROR("{ callvalue pop }", ParserError, "Call or assignment expected"); + SUCCESS_STRICT("{ pop(callvalue()) }"); + BOOST_CHECK(successParse("{ switch callvalue case 0 {} }")); + CHECK_STRICT_ERROR("{ switch callvalue case 0 {} }", ParserError, "Non-functional instructions are not allowed in this context."); +} + +BOOST_AUTO_TEST_CASE(no_labels_in_strict) +{ + BOOST_CHECK(successParse("{ a: }")); + CHECK_STRICT_ERROR("{ a: }", ParserError, "Labels are not supported"); +} + +BOOST_AUTO_TEST_CASE(no_stack_assign_in_strict) +{ + BOOST_CHECK(successParse("{ let x 4 =: x }")); + CHECK_STRICT_ERROR("{ let x 4 =: x }", ParserError, "Call or assignment expected."); +} + +BOOST_AUTO_TEST_CASE(no_dup_swap_in_strict) +{ + BOOST_CHECK(successParse("{ swap1 }")); + CHECK_STRICT_ERROR("{ swap1 }", ParserError, "Call or assignment expected."); + BOOST_CHECK(successParse("{ dup1 pop }")); + CHECK_STRICT_ERROR("{ dup1 pop }", ParserError, "Call or assignment expected."); + BOOST_CHECK(successParse("{ swap2 }")); + CHECK_STRICT_ERROR("{ swap2 }", ParserError, "Call or assignment expected."); + BOOST_CHECK(successParse("{ dup2 pop }")); + CHECK_STRICT_ERROR("{ dup2 pop }", ParserError, "Call or assignment expected."); + CHECK_PARSE_ERROR("{ switch dup1 case 0 {} }", ParserError, "Instruction \"dup1\" not allowed in this context"); + CHECK_STRICT_ERROR("{ switch dup1 case 0 {} }", ParserError, "Instruction \"dup1\" not allowed in this context"); +} + +BOOST_AUTO_TEST_SUITE_END() + BOOST_AUTO_TEST_SUITE(Printing) BOOST_AUTO_TEST_CASE(print_smoke) diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index fa0f1e59..e48624e5 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -480,6 +480,41 @@ BOOST_AUTO_TEST_CASE(filename_with_colon) BOOST_CHECK_EQUAL(dev::jsonCompactPrint(contract["abi"]), "[]"); } +BOOST_AUTO_TEST_CASE(library_filename_with_colon) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "outputSelection": { + "fileA": { + "A": [ + "evm.bytecode" + ] + } + } + }, + "sources": { + "fileA": { + "content": "import \"git:library.sol\"; contract A { function f() returns (uint) { return L.g(); } }" + }, + "git:library.sol": { + "content": "library L { function g() returns (uint) { return 1; } }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "fileA", "A"); + BOOST_CHECK(contract.isObject()); + BOOST_CHECK(contract["evm"]["bytecode"].isObject()); + BOOST_CHECK(contract["evm"]["bytecode"]["linkReferences"].isObject()); + BOOST_CHECK(contract["evm"]["bytecode"]["linkReferences"]["git:library.sol"].isObject()); + BOOST_CHECK(contract["evm"]["bytecode"]["linkReferences"]["git:library.sol"]["L"].isArray()); + BOOST_CHECK(contract["evm"]["bytecode"]["linkReferences"]["git:library.sol"]["L"][0].isObject()); +} + BOOST_AUTO_TEST_SUITE_END() |