diff options
author | chriseth <c@ethdev.com> | 2016-02-22 09:13:41 +0800 |
---|---|---|
committer | chriseth <c@ethdev.com> | 2016-03-30 08:37:00 +0800 |
commit | 949b00ed591303c531ed8fa73087b710b7a554de (patch) | |
tree | 182664f2545e6211d7994ef90a1e7746d5482981 /libsolidity/inlineasm | |
parent | 8236732e9a5d2535afd3a3573a70d5aab3da3efe (diff) | |
download | dexon-solidity-949b00ed591303c531ed8fa73087b710b7a554de.tar.gz dexon-solidity-949b00ed591303c531ed8fa73087b710b7a554de.tar.zst dexon-solidity-949b00ed591303c531ed8fa73087b710b7a554de.zip |
Parsing for inline assembly.
Diffstat (limited to 'libsolidity/inlineasm')
-rw-r--r-- | libsolidity/inlineasm/AsmData.h | 70 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmParser.cpp | 212 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmParser.h | 55 |
3 files changed, 337 insertions, 0 deletions
diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h new file mode 100644 index 00000000..a38a9d36 --- /dev/null +++ b/libsolidity/inlineasm/AsmData.h @@ -0,0 +1,70 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2016 + * Parsed inline assembly to be used by the AST + */ + +#pragma once + +#include <boost/variant.hpp> +#include <libevmcore/Instruction.h> + +namespace dev +{ +namespace solidity +{ + +class AsmData +{ +public: + /// Direct EVM instruction (except PUSHi and JUMPDEST) + struct Instruction { eth::Instruction instruction; }; + /// Literal number or string (up to 32 bytes) + struct Literal { bool isNumber; std::string value; }; + /// External / internal identifier or label reference + struct Identifier { std::string name; }; + struct FunctionalInstruction; + /// Jump label ("name:") + struct Label { std::string name; }; + /// Assignemnt (":= x", moves stack top into x, potentially multiple slots) + struct Assignment { Identifier variableName; }; + struct FunctionalAssignment; + struct VariableDeclaration; + struct Block; + using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionalInstruction, VariableDeclaration, Block>; + /// Functional assignment ("x := mload(20)", expects push-1-expression on the right hand + /// side and requires x to occupy exactly one stack slot. + struct FunctionalAssignment { Identifier variableName; std::shared_ptr<Statement> value; }; + /// Functional instruction, e.g. "mul(mload(20), add(2, x))" + struct FunctionalInstruction { Instruction instruction; std::vector<Statement> arguments; }; + /// Block-scope variable declaration ("let x := mload(20)"), non-hoisted + struct VariableDeclaration { std::string name; std::shared_ptr<Statement> value; }; + /// Block that creates a scope (frees declared stack variables) + struct Block { std::vector<Statement> statements; }; + + AsmData(Block&& _statements): m_statements(_statements) {} + + Block const& statements() const { return m_statements; } + +private: + Block m_statements; +}; + +} +} diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp new file mode 100644 index 00000000..28fd5354 --- /dev/null +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -0,0 +1,212 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2016 + * Solidity inline assembly parser. + */ + +#include <libsolidity/inlineasm/AsmParser.h> +#include <ctype.h> +#include <algorithm> +#include <libevmcore/Instruction.h> +#include <libsolidity/parsing/Scanner.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +shared_ptr<AsmData> InlineAssemblyParser::parse(std::shared_ptr<Scanner> const& _scanner) +{ + try + { + m_scanner = _scanner; + return make_shared<AsmData>(parseBlock()); + } + catch (FatalError const&) + { + if (m_errors.empty()) + throw; // Something is weird here, rather throw again. + } + return nullptr; +} + +AsmData::Block InlineAssemblyParser::parseBlock() +{ + expectToken(Token::LBrace); + AsmData::Block block; + while (m_scanner->currentToken() != Token::RBrace) + block.statements.emplace_back(parseStatement()); + m_scanner->next(); + return block; +} + +AsmData::Statement InlineAssemblyParser::parseStatement() +{ + switch (m_scanner->currentToken()) + { + case Token::Let: + return parseVariableDeclaration(); + case Token::LBrace: + return parseBlock(); + case Token::Assign: + { + m_scanner->next(); + expectToken(Token::Colon); + string name = m_scanner->currentLiteral(); + expectToken(Token::Identifier); + return AsmData::Assignment{AsmData::Identifier{name}}; + } + default: + break; + } + // Options left: + // Simple instruction (might turn into functional), + // literal, + // identifier (might turn into label or functional assignment) + AsmData::Statement statement(parseElementaryOperation()); + switch (m_scanner->currentToken()) + { + case Token::LParen: + return parseFunctionalInstruction(statement); + case Token::Colon: + { + if (statement.type() != typeid(AsmData::Identifier)) + fatalParserError("Label name / variable name must precede \":\"."); + string const& name = boost::get<AsmData::Identifier>(statement).name; + m_scanner->next(); + if (m_scanner->currentToken() == Token::Assign) + { + // functional assignment + m_scanner->next(); + unique_ptr<AsmData::Statement> value; + value.reset(new AsmData::Statement(parseExpression())); + return AsmData::FunctionalAssignment{{move(name)}, move(value)}; + } + else + // label + return AsmData::Label{name}; + } + default: + break; + } + return statement; +} + +AsmData::Statement InlineAssemblyParser::parseExpression() +{ + AsmData::Statement operation = parseElementaryOperation(true); + if (m_scanner->currentToken() == Token::LParen) + return parseFunctionalInstruction(operation); + else + return operation; +} + +AsmData::Statement InlineAssemblyParser::parseElementaryOperation(bool _onlySinglePusher) +{ + // Allowed instructions, lowercase names. + static map<string, eth::Instruction> s_instructions; + if (s_instructions.empty()) + for (auto const& instruction: eth::c_instructions) + { + if ( + instruction.second == eth::Instruction::JUMPDEST || + (eth::Instruction::PUSH1 <= instruction.second && instruction.second <= eth::Instruction::PUSH32) + ) + continue; + string name = instruction.first; + transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); }); + s_instructions[name] = instruction.second; + } + + //@TODO track location + + switch (m_scanner->currentToken()) + { + case Token::Identifier: + { + string literal = m_scanner->currentLiteral(); + // first search the set of instructions. + if (s_instructions.count(literal)) + { + eth::Instruction const& instr = s_instructions[literal]; + if (_onlySinglePusher) + { + eth::InstructionInfo info = eth::instructionInfo(instr); + if (info.ret != 1) + fatalParserError("Instruction " + info.name + " not allowed in this context."); + } + m_scanner->next(); + return AsmData::Instruction{instr}; + } + else + m_scanner->next(); + return AsmData::Identifier{literal}; + break; + } + case Token::StringLiteral: + case Token::Number: + { + AsmData::Literal literal{ + m_scanner->currentToken() == Token::Number, + m_scanner->currentLiteral() + }; + m_scanner->next(); + return literal; + } + default: + break; + } + fatalParserError("Expected elementary inline assembly operation."); + return {}; +} + +AsmData::VariableDeclaration InlineAssemblyParser::parseVariableDeclaration() +{ + expectToken(Token::Let); + string name = m_scanner->currentLiteral(); + expectToken(Token::Identifier); + expectToken(Token::Colon); + expectToken(Token::Assign); + unique_ptr<AsmData::Statement> value; + value.reset(new AsmData::Statement(parseExpression())); + return AsmData::VariableDeclaration{name, move(value)}; +} + +AsmData::FunctionalInstruction InlineAssemblyParser::parseFunctionalInstruction(AsmData::Statement const& _instruction) +{ + if (_instruction.type() != typeid(AsmData::Instruction)) + fatalParserError("Assembly instruction required in front of \"(\")"); + eth::Instruction instr = boost::get<AsmData::Instruction>(_instruction).instruction; + eth::InstructionInfo instrInfo = eth::instructionInfo(instr); + if (eth::Instruction::DUP1 <= instr && instr <= eth::Instruction::DUP16) + fatalParserError("DUPi instructions not allowed for functional notation"); + if (eth::Instruction::SWAP1 <= instr && instr <= eth::Instruction::SWAP16) + fatalParserError("SWAPi instructions not allowed for functional notation"); + + expectToken(Token::LParen); + vector<AsmData::Statement> arguments; + unsigned args = unsigned(instrInfo.args); + for (unsigned i = 0; i < args; ++i) + { + arguments.push_back(parseExpression()); + if (i != args - 1) + expectToken(Token::Comma); + } + expectToken(Token::RParen); + return AsmData::FunctionalInstruction{{instr}, move(arguments)}; +} diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h new file mode 100644 index 00000000..fe84470d --- /dev/null +++ b/libsolidity/inlineasm/AsmParser.h @@ -0,0 +1,55 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2016 + * Solidity inline assembly parser. + */ + +#pragma once + +#include <memory> +#include <vector> +#include <libsolidity/inlineasm/AsmData.h> +#include <libsolidity/parsing/ParserBase.h> + +namespace dev +{ +namespace solidity +{ + +class InlineAssemblyParser: public ParserBase +{ +public: + InlineAssemblyParser(ErrorList& _errors): ParserBase(_errors) {} + + /// Parses an inline assembly block starting with `{` and ending with `}`. + /// @returns an empty shared pointer on error. + std::shared_ptr<AsmData> parse(std::shared_ptr<Scanner> const& _scanner); + +protected: + AsmData::Block parseBlock(); + AsmData::Statement parseStatement(); + /// Parses a functional expression that has to push exactly one stack element + AsmData::Statement parseExpression(); + AsmData::Statement parseElementaryOperation(bool _onlySinglePusher = false); + AsmData::VariableDeclaration parseVariableDeclaration(); + AsmData::FunctionalInstruction parseFunctionalInstruction(AsmData::Statement const& _instruction); +}; + +} +} |