diff options
28 files changed, 218 insertions, 381 deletions
diff --git a/docs/assembly.rst b/docs/assembly.rst index 978e71e3..443cb7da 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -418,6 +418,9 @@ changes during the call, and thus references to local variables will be wrong. Labels ------ +.. note:: + Labels are deprecated. Please use functions, loops, if or switch statements instead. + Another problem in EVM assembly is that ``jump`` and ``jumpi`` use absolute addresses which can change easily. Solidity inline assembly provides labels to make the use of jumps easier. Note that labels are a low-level feature and it is possible to write @@ -519,6 +522,10 @@ is performed by replacing the variable's value on the stack by the new value. =: v // instruction style assignment, puts the result of sload(10) into v } +.. note:: + Instruction-style assignment is deprecated. + + If -- @@ -693,9 +700,9 @@ the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like ``7 y x add mul`` because in the first form, it is much easier to see which operand is used for which opcode. -The second goal is achieved by introducing a desugaring phase that only removes -the higher level constructs in a very regular way and still allows inspecting -the generated low-level assembly code. The only non-local operation performed +The second goal is achieved by compiling the +higher level constructs to bytecode in a very regular way. +The only non-local operation performed by the assembler is name lookup of user-defined identifiers (functions, variables, ...), which follow very simple and regular scoping rules and cleanup of local variables from the stack. @@ -716,8 +723,6 @@ keep track of the current so-called stack height. Since all local variables are removed at the end of a block, the stack height before and after the block should be the same. If this is not the case, a warning is issued. -Why do we use higher-level constructs like ``switch``, ``for`` and functions: - Using ``switch``, ``for`` and functions, it should be possible to write complex code without using ``jump`` or ``jumpi`` manually. This makes it much easier to analyze the control flow, which allows for improved formal @@ -726,13 +731,11 @@ verification and optimization. Furthermore, if manual jumps are allowed, computing the stack height is rather complicated. The position of all local variables on the stack needs to be known, otherwise neither references to local variables nor removing local variables automatically -from the stack at the end of a block will work properly. The desugaring -mechanism correctly inserts operations at unreachable blocks that adjust the -stack height properly in case of jumps that do not have a continuing control flow. +from the stack at the end of a block will work properly. Example: -We will follow an example compilation from Solidity to desugared assembly. +We will follow an example compilation from Solidity to assembly. We consider the runtime bytecode of the following Solidity program:: pragma solidity ^0.4.16; @@ -772,99 +775,9 @@ The following assembly will be generated:: } } -After the desugaring phase it looks as follows:: - - { - mstore(0x40, 0x60) - { - let $0 := div(calldataload(0), exp(2, 226)) - jumpi($case1, eq($0, 0xb3de648b)) - jump($caseDefault) - $case1: - { - // the function call - we put return label and arguments on the stack - $ret1 calldataload(4) jump(f) - // This is unreachable code. Opcodes are added that mirror the - // effect of the function on the stack height: Arguments are - // removed and return values are introduced. - pop pop - let r := 0 - $ret1: // the actual return point - $ret2 0x20 jump($allocate) - pop pop let ret := 0 - $ret2: - mstore(ret, r) - return(ret, 0x20) - // although it is useless, the jump is automatically inserted, - // since the desugaring process is a purely syntactic operation that - // does not analyze control-flow - jump($endswitch) - } - $caseDefault: - { - revert(0, 0) - jump($endswitch) - } - $endswitch: - } - jump($afterFunction) - allocate: - { - // we jump over the unreachable code that introduces the function arguments - jump($start) - let $retpos := 0 let size := 0 - $start: - // output variables live in the same scope as the arguments and is - // actually allocated. - let pos := 0 - { - pos := mload(0x40) - mstore(0x40, add(pos, size)) - } - // This code replaces the arguments by the return values and jumps back. - swap1 pop swap1 jump - // Again unreachable code that corrects stack height. - 0 0 - } - f: - { - jump($start) - let $retpos := 0 let x := 0 - $start: - let y := 0 - { - let i := 0 - $for_begin: - jumpi($for_end, iszero(lt(i, x))) - { - y := mul(2, y) - } - $for_continue: - { i := add(i, 1) } - jump($for_begin) - $for_end: - } // Here, a pop instruction will be inserted for i - swap1 pop swap1 jump - 0 0 - } - $afterFunction: - stop - } - - -Assembly happens in four stages: -1. Parsing -2. Desugaring (removes switch, for and functions) -3. Opcode stream generation -4. Bytecode generation - -We will specify steps one to three in a pseudo-formal way. More formal -specifications will follow. - - -Parsing / Grammar ------------------ +Assembly Grammar +---------------- The tasks of the parser are the following: @@ -922,160 +835,3 @@ Grammar:: StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' HexNumber = '0x' [0-9a-fA-F]+ DecimalNumber = [0-9]+ - - -Desugaring ----------- - -An AST transformation removes for, switch and function constructs. The result -is still parseable by the same parser, but it will not use certain constructs. -If jumpdests are added that are only jumped to and not continued at, information -about the stack content is added, unless no local variables of outer scopes are -accessed or the stack height is the same as for the previous instruction. - -Pseudocode:: - - desugar item: AST -> AST = - match item { - AssemblyFunctionDefinition('function' name '(' arg1, ..., argn ')' '->' ( '(' ret1, ..., retm ')' body) -> - <name>: - { - jump($<name>_start) - let $retPC := 0 let argn := 0 ... let arg1 := 0 - $<name>_start: - let ret1 := 0 ... let retm := 0 - { desugar(body) } - swap and pop items so that only ret1, ... retm, $retPC are left on the stack - jump - 0 (1 + n times) to compensate removal of arg1, ..., argn and $retPC - } - AssemblyFor('for' { init } condition post body) -> - { - init // cannot be its own block because we want variable scope to extend into the body - // find I such that there are no labels $forI_* - $forI_begin: - jumpi($forI_end, iszero(condition)) - { body } - $forI_continue: - { post } - jump($forI_begin) - $forI_end: - } - 'break' -> - { - // find nearest enclosing scope with label $forI_end - pop all local variables that are defined at the current point - but not at $forI_end - jump($forI_end) - 0 (as many as variables were removed above) - } - 'continue' -> - { - // find nearest enclosing scope with label $forI_continue - pop all local variables that are defined at the current point - but not at $forI_continue - jump($forI_continue) - 0 (as many as variables were removed above) - } - AssemblySwitch(switch condition cases ( default: defaultBlock )? ) -> - { - // find I such that there is no $switchI* label or variable - let $switchI_value := condition - for each of cases match { - case val: -> jumpi($switchI_caseJ, eq($switchI_value, val)) - } - if default block present: -> - { defaultBlock jump($switchI_end) } - for each of cases match { - case val: { body } -> $switchI_caseJ: { body jump($switchI_end) } - } - $switchI_end: - } - FunctionalAssemblyExpression( identifier(arg1, arg2, ..., argn) ) -> - { - if identifier is function <name> with n args and m ret values -> - { - // find I such that $funcallI_* does not exist - $funcallI_return argn ... arg2 arg1 jump(<name>) - pop (n + 1 times) - if the current context is `let (id1, ..., idm) := f(...)` -> - let id1 := 0 ... let idm := 0 - $funcallI_return: - else -> - 0 (m times) - $funcallI_return: - turn the functional expression that leads to the function call - into a statement stream - } - else -> desugar(children of node) - } - default node -> - desugar(children of node) - } - -Opcode Stream Generation ------------------------- - -During opcode stream generation, we keep track of the current stack height -in a counter, -so that accessing stack variables by name is possible. The stack height is modified with every opcode -that modifies the stack and with every label that is annotated with a stack -adjustment. Every time a new -local variable is introduced, it is registered together with the current -stack height. If a variable is accessed (either for copying its value or for -assignment), the appropriate ``DUP`` or ``SWAP`` instruction is selected depending -on the difference between the current stack height and the -stack height at the point the variable was introduced. - -Pseudocode:: - - codegen item: AST -> opcode_stream = - match item { - AssemblyBlock({ items }) -> - join(codegen(item) for item in items) - if last generated opcode has continuing control flow: - POP for all local variables registered at the block (including variables - introduced by labels) - warn if the stack height at this point is not the same as at the start of the block - Identifier(id) -> - lookup id in the syntactic stack of blocks - match type of id - Local Variable -> - DUPi where i = 1 + stack_height - stack_height_of_identifier(id) - Label -> - // reference to be resolved during bytecode generation - PUSH<bytecode position of label> - SubAssembly -> - PUSH<bytecode position of subassembly data> - FunctionalAssemblyExpression(id ( arguments ) ) -> - join(codegen(arg) for arg in arguments.reversed()) - id (which has to be an opcode, might be a function name later) - AssemblyLocalDefinition(let (id1, ..., idn) := expr) -> - register identifiers id1, ..., idn as locals in current block at current stack height - codegen(expr) - assert that expr returns n items to the stack - FunctionalAssemblyAssignment((id1, ..., idn) := expr) -> - lookup id1, ..., idn in the syntactic stack of blocks, assert that they are variables - codegen(expr) - for j = n, ..., i: - SWAPi where i = 1 + stack_height - stack_height_of_identifier(idj) - POP - AssemblyAssignment(=: id) -> - look up id in the syntactic stack of blocks, assert that it is a variable - SWAPi where i = 1 + stack_height - stack_height_of_identifier(id) - POP - LabelDefinition(name:) -> - JUMPDEST - NumberLiteral(num) -> - PUSH<num interpreted as decimal and right-aligned> - HexLiteral(lit) -> - PUSH32<lit interpreted as hex and left-aligned> - StringLiteral(lit) -> - PUSH32<lit utf-8 encoded and left-aligned> - SubAssembly(assembly <name> block) -> - append codegen(block) at the end of the code - dataSize(<name>) -> - assert that <name> is a subassembly -> - PUSH32<size of code generated from subassembly <name>> - linkerSymbol(<lit>) -> - PUSH32<zeros> and append position to linker table - } diff --git a/docs/conf.py b/docs/conf.py index ca8c0fec..3bbee671 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ master_doc = 'index' # General information about the project. project = 'Solidity' -copyright = '2016-2017, Ethereum' +copyright = '2016-2018, Ethereum' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/contracts.rst b/docs/contracts.rst index 17eb23f7..5c298274 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -1066,12 +1066,15 @@ Multiple Inheritance and Linearization Languages that allow multiple inheritance have to deal with several problems. One is the `Diamond Problem <https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem>`_. -Solidity follows the path of Python and uses "`C3 Linearization <https://en.wikipedia.org/wiki/C3_linearization>`_" +Solidity is similar to Python in that it uses "`C3 Linearization <https://en.wikipedia.org/wiki/C3_linearization>`_" to force a specific order in the DAG of base classes. This results in the desirable property of monotonicity but disallows some inheritance graphs. Especially, the order in which the base classes are given in the ``is`` directive is -important. In the following code, Solidity will give the +important: You have to list the direct base contracts +in the order from "most base-like" to "most derived". +Note that this order is different from the one used in Python. +In the following code, Solidity will give the error "Linearization of inheritance graph impossible". :: @@ -1089,9 +1092,6 @@ The reason for this is that ``C`` requests ``X`` to override ``A`` requests to override ``X``, which is a contradiction that cannot be resolved. -A simple rule to remember is to specify the base classes in -the order from "most base-like" to "most derived". - Inheriting Different Kinds of Members of the Same Name ====================================================== diff --git a/docs/grammar.txt b/docs/grammar.txt index b4ca5ca9..565db9a4 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -16,7 +16,7 @@ ContractPart = StateVariableDeclaration | UsingForDeclaration InheritanceSpecifier = UserDefinedTypeName ( '(' Expression ( ',' Expression )* ')' )? -StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' )? Identifier ('=' Expression)? ';' +StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' )* Identifier ('=' Expression)? ';' UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';' StructDefinition = 'struct' Identifier '{' ( VariableDeclaration ';' (VariableDeclaration ';')* ) '}' diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index 546767e4..f6038f7d 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -66,7 +66,7 @@ of votes. Proposal[] public proposals; /// Create a new ballot to choose one of `proposalNames`. - function Ballot(bytes32[] proposalNames) public { + constructor(bytes32[] proposalNames) public { chairperson = msg.sender; voters[chairperson].weight = 1; @@ -256,7 +256,7 @@ activate themselves. /// Create a simple auction with `_biddingTime` /// seconds bidding time on behalf of the /// beneficiary address `_beneficiary`. - function SimpleAuction( + constructor( uint _biddingTime, address _beneficiary ) public { @@ -418,7 +418,7 @@ high or low invalid bids. modifier onlyBefore(uint _time) { require(now < _time); _; } modifier onlyAfter(uint _time) { require(now > _time); _; } - function BlindAuction( + constructor( uint _biddingTime, uint _revealTime, address _beneficiary @@ -553,7 +553,7 @@ Safe Remote Purchase // Ensure that `msg.value` is an even number. // Division will truncate if it is an odd number. // Check via multiplication that it wasn't an odd number. - function Purchase() public payable { + constructor() public payable { seller = msg.sender; value = msg.value / 2; require((2 * value) == msg.value, "Value has to be even."); @@ -602,7 +602,7 @@ Safe Remote Purchase { emit Aborted(); state = State.Inactive; - seller.transfer(this.balance); + seller.transfer(address(this).balance); } /// Confirm the purchase as buyer. @@ -637,7 +637,7 @@ Safe Remote Purchase // block the refund - the withdraw pattern should be used. buyer.transfer(value); - seller.transfer(this.balance); + seller.transfer(address(this).balance); } } diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst index d57f1703..7a6317eb 100644 --- a/docs/structure-of-a-contract.rst +++ b/docs/structure-of-a-contract.rst @@ -129,7 +129,7 @@ Structs are custom defined types that can group several variables (see Enum Types ========== -Enums can be used to create custom types with a finite set of values (see +Enums can be used to create custom types with a finite set of 'constant values' (see :ref:`enums` in types section). :: diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 5af618ff..a3a4d6b6 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -26,27 +26,35 @@ using namespace std; using namespace dev; using namespace dev::eth; +static_assert(sizeof(size_t) <= 8, "size_t must be at most 64-bits wide"); + AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const { assertThrow(data() < (u256(1) << 64), Exception, "Tag already has subassembly set."); - assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); + size_t tag = size_t(u256(data()) & 0xffffffffffffffffULL); AssemblyItem r = *this; r.m_type = PushTag; - r.setPushTagSubIdAndTag(_subId, size_t(data())); + r.setPushTagSubIdAndTag(_subId, tag); return r; } pair<size_t, size_t> AssemblyItem::splitForeignPushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); - return make_pair(size_t((data()) / (u256(1) << 64)) - 1, size_t(data())); + u256 combined = u256(data()); + size_t subId = size_t((combined >> 64) - 1); + size_t tag = size_t(combined & 0xffffffffffffffffULL); + return make_pair(subId, tag); } void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag) { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); - setData(_tag + (u256(_subId + 1) << 64)); + u256 data = _tag; + if (_subId != size_t(-1)) + data |= (u256(_subId) + 1) << 64; + setData(data); } unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const diff --git a/libevmasm/ConstantOptimiser.h b/libevmasm/ConstantOptimiser.h index 9b60b26b..f0deb387 100644 --- a/libevmasm/ConstantOptimiser.h +++ b/libevmasm/ConstantOptimiser.h @@ -67,6 +67,7 @@ public: explicit ConstantOptimisationMethod(Params const& _params, u256 const& _value): m_params(_params), m_value(_value) {} + virtual ~ConstantOptimisationMethod() = default; virtual bigint gasNeeded() const = 0; /// Executes the method, potentially appending to the assembly and returns a vector of /// assembly items the constant should be relpaced with in one sweep. diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index be788ddb..dc116f88 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -39,7 +39,7 @@ enum class Instruction: uint8_t { STOP = 0x00, ///< halts execution ADD, ///< addition operation - MUL, ///< mulitplication operation + MUL, ///< multiplication operation SUB, ///< subtraction operation DIV, ///< integer division operation SDIV, ///< signed integer division operation @@ -50,11 +50,11 @@ enum class Instruction: uint8_t EXP, ///< exponential operation SIGNEXTEND, ///< extend length of signed integer - LT = 0x10, ///< less-than comparision - GT, ///< greater-than comparision - SLT, ///< signed less-than comparision - SGT, ///< signed greater-than comparision - EQ, ///< equality comparision + LT = 0x10, ///< less-than comparison + GT, ///< greater-than comparison + SLT, ///< signed less-than comparison + SGT, ///< signed greater-than comparison + EQ, ///< equality comparison ISZERO, ///< simple not operator AND, ///< bitwise AND operation OR, ///< bitwise OR operation @@ -293,7 +293,7 @@ struct InstructionInfo /// Information on all the instructions. InstructionInfo instructionInfo(Instruction _inst); -/// check whether instructions exists +/// check whether instructions exists. bool isValidInstruction(Instruction _inst); /// Convert from string mnemonic to Instruction type. diff --git a/libevmasm/PeepholeOptimiser.h b/libevmasm/PeepholeOptimiser.h index a74cc8b3..a651143d 100644 --- a/libevmasm/PeepholeOptimiser.h +++ b/libevmasm/PeepholeOptimiser.h @@ -34,6 +34,7 @@ using AssemblyItems = std::vector<AssemblyItem>; class PeepholeOptimisationMethod { public: + virtual ~PeepholeOptimisationMethod() = default; virtual size_t windowSize() const; virtual bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out); }; @@ -42,6 +43,7 @@ class PeepholeOptimiser { public: explicit PeepholeOptimiser(AssemblyItems& _items): m_items(_items) {} + virtual ~PeepholeOptimiser() = default; bool optimise(); diff --git a/libjulia/optimiser/ASTCopier.h b/libjulia/optimiser/ASTCopier.h index 36a1ced5..8681f2e0 100644 --- a/libjulia/optimiser/ASTCopier.h +++ b/libjulia/optimiser/ASTCopier.h @@ -37,6 +37,7 @@ namespace julia class ExpressionCopier: public boost::static_visitor<Expression> { public: + virtual ~ExpressionCopier() = default; virtual Expression operator()(Literal const& _literal) = 0; virtual Expression operator()(Identifier const& _identifier) = 0; virtual Expression operator()(FunctionalInstruction const& _instr) = 0; @@ -46,6 +47,7 @@ public: class StatementCopier: public boost::static_visitor<Statement> { public: + virtual ~StatementCopier() = default; virtual Statement operator()(ExpressionStatement const& _statement) = 0; virtual Statement operator()(Instruction const& _instruction) = 0; virtual Statement operator()(Label const& _label) = 0; @@ -66,6 +68,7 @@ public: class ASTCopier: public ExpressionCopier, public StatementCopier { public: + virtual ~ASTCopier() = default; virtual Expression operator()(Literal const& _literal) override; virtual Statement operator()(Instruction const& _instruction) override; virtual Expression operator()(Identifier const& _identifier) override; diff --git a/libjulia/optimiser/ASTWalker.h b/libjulia/optimiser/ASTWalker.h index 97891381..f09c2ff1 100644 --- a/libjulia/optimiser/ASTWalker.h +++ b/libjulia/optimiser/ASTWalker.h @@ -42,6 +42,7 @@ namespace julia class ASTWalker: public boost::static_visitor<> { public: + virtual ~ASTWalker() = default; virtual void operator()(Literal const&) {} virtual void operator()(Instruction const&) { solAssert(false, ""); } virtual void operator()(Identifier const&) {} @@ -82,6 +83,7 @@ protected: class ASTModifier: public boost::static_visitor<> { public: + virtual ~ASTModifier() = default; virtual void operator()(Literal&) {} virtual void operator()(Instruction&) { solAssert(false, ""); } virtual void operator()(Identifier&) {} diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index a53987bf..fa0d6921 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -146,6 +146,7 @@ private: class Scopable { public: + virtual ~Scopable() = default; /// @returns the scope this declaration resides in. Can be nullptr if it is the global scope. /// Available only after name and type resolution step. ASTNode const* scope() const { return m_scope; } @@ -307,6 +308,7 @@ private: class VariableScope { public: + virtual ~VariableScope() = default; void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); } std::vector<VariableDeclaration const*> const& localVariables() const { return m_localVariables; } @@ -320,6 +322,7 @@ private: class Documented { public: + virtual ~Documented() = default; explicit Documented(ASTPointer<ASTString> const& _documentation): m_documentation(_documentation) {} /// @return A shared pointer of an ASTString. @@ -336,6 +339,7 @@ protected: class ImplementationOptional { public: + virtual ~ImplementationOptional() = default; explicit ImplementationOptional(bool _implemented): m_implemented(_implemented) {} /// @return whether this node is fully implemented or not diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index b1389f0f..6c0ce6f8 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -43,6 +43,7 @@ namespace solidity class ASTVisitor { public: + virtual ~ASTVisitor() = default; virtual bool visit(SourceUnit& _node) { return visitNode(_node); } virtual bool visit(PragmaDirective& _node) { return visitNode(_node); } virtual bool visit(ImportDirective& _node) { return visitNode(_node); } @@ -147,6 +148,7 @@ protected: class ASTConstVisitor { public: + virtual ~ASTConstVisitor() = default; virtual bool visit(SourceUnit const& _node) { return visitNode(_node); } virtual bool visit(PragmaDirective const& _node) { return visitNode(_node); } virtual bool visit(ImportDirective const& _node) { return visitNode(_node); } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 425e5045..11d7160c 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -627,8 +627,7 @@ bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool FixedPointType::isExplicitlyConvertibleTo(Type const& _convertTo) const { return _convertTo.category() == category() || - _convertTo.category() == Category::Integer || - _convertTo.category() == Category::FixedBytes; + (_convertTo.category() == Category::Integer && !dynamic_cast<IntegerType const&>(_convertTo).isAddress()); } TypePointer FixedPointType::unaryOperatorResult(Token::Value _operator) const @@ -682,13 +681,7 @@ bigint FixedPointType::minIntegerValue() const TypePointer FixedPointType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const { - if ( - _other->category() != Category::RationalNumber && - _other->category() != category() && - _other->category() != Category::Integer - ) - return TypePointer(); - auto commonType = Type::commonType(shared_from_this(), _other); //might be fixed point or integer + auto commonType = Type::commonType(shared_from_this(), _other); if (!commonType) return TypePointer(); @@ -696,19 +689,16 @@ TypePointer FixedPointType::binaryOperatorResult(Token::Value _operator, TypePoi // All fixed types can be compared if (Token::isCompareOp(_operator)) return commonType; - if (Token::isBitOp(_operator) || Token::isBooleanOp(_operator)) + if (Token::isBitOp(_operator) || Token::isBooleanOp(_operator) || _operator == Token::Exp) return TypePointer(); - if (auto fixType = dynamic_pointer_cast<FixedPointType const>(commonType)) - { - if (Token::Exp == _operator) - return TypePointer(); - } - else if (auto intType = dynamic_pointer_cast<IntegerType const>(commonType)) - if (intType->isAddress()) - return TypePointer(); return commonType; } +std::shared_ptr<IntegerType> FixedPointType::asIntegerType() const +{ + return std::make_shared<IntegerType>(numBits(), isSigned() ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned); +} + tuple<bool, rational> RationalNumberType::parseRational(string const& _value) { rational value; @@ -1148,7 +1138,7 @@ u256 RationalNumberType::literalValue(Literal const*) const auto fixed = fixedPointType(); solAssert(fixed, ""); int fractionalDigits = fixed->fractionalDigits(); - shiftedValue = (m_value.numerator() / m_value.denominator()) * pow(bigint(10), fractionalDigits); + shiftedValue = m_value.numerator() * pow(bigint(10), fractionalDigits) / m_value.denominator(); } // we ignore the literal and hope that the type was correctly determined @@ -1274,17 +1264,12 @@ bool StringLiteralType::isValidUTF8() const return dev::validateUTF8(m_value); } -shared_ptr<FixedBytesType> FixedBytesType::smallestTypeForLiteral(string const& _literal) -{ - if (_literal.length() <= 32) - return make_shared<FixedBytesType>(_literal.length()); - return shared_ptr<FixedBytesType>(); -} - FixedBytesType::FixedBytesType(int _bytes): m_bytes(_bytes) { - solAssert(m_bytes >= 0 && m_bytes <= 32, - "Invalid byte number for fixed bytes type: " + dev::toString(m_bytes)); + solAssert( + m_bytes > 0 && m_bytes <= 32, + "Invalid byte number for fixed bytes type: " + dev::toString(m_bytes) + ); } bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 345f84a1..ca6822c9 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -138,6 +138,7 @@ private: class Type: private boost::noncopyable, public std::enable_shared_from_this<Type> { public: + virtual ~Type() = default; enum class Category { Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, @@ -396,6 +397,9 @@ public: /// smallest value in general. bigint minIntegerValue() const; + /// @returns the smallest integer type that can hold this type with fractional parts shifted to integers. + std::shared_ptr<IntegerType> asIntegerType() const; + private: int m_totalBits; int m_fractionalDigits; @@ -502,10 +506,6 @@ class FixedBytesType: public Type public: virtual Category category() const override { return Category::FixedBytes; } - /// @returns the smallest bytes type for the given literal or an empty pointer - /// if no type fits. - static std::shared_ptr<FixedBytesType> smallestTypeForLiteral(std::string const& _literal); - explicit FixedBytesType(int _bytes); virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; diff --git a/libsolidity/codegen/LValue.h b/libsolidity/codegen/LValue.h index f8b68362..c576f9de 100644 --- a/libsolidity/codegen/LValue.h +++ b/libsolidity/codegen/LValue.h @@ -49,6 +49,7 @@ protected: m_context(_compilerContext), m_dataType(_dataType) {} public: + virtual ~LValue() {} /// @returns the number of stack slots occupied by the lvalue reference virtual unsigned sizeOnStack() const { return 1; } /// Copies the value of the current lvalue to the top of the stack and, if @a _remove is true, diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index e127bb55..16796684 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -193,6 +193,7 @@ DEV_SIMPLE_EXCEPTION(SolverError); class SolverInterface { public: + virtual ~SolverInterface() = default; virtual void reset() = 0; virtual void push() = 0; diff --git a/libsolidity/formal/SymbolicVariable.h b/libsolidity/formal/SymbolicVariable.h index e4e4ea8d..e29ded26 100644 --- a/libsolidity/formal/SymbolicVariable.h +++ b/libsolidity/formal/SymbolicVariable.h @@ -40,6 +40,7 @@ public: Declaration const& _decl, smt::SolverInterface& _interface ); + virtual ~SymbolicVariable() = default; smt::Expression operator()(int _seq) const { diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 618a0896..a9ee9016 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -1057,16 +1057,16 @@ ASTPointer<EmitStatement> Parser::parseEmitStatement(ASTPointer<ASTString> const if (m_scanner->currentToken() != Token::Identifier) fatalParserError("Expected event name or path."); - vector<ASTPointer<PrimaryExpression>> path; + IndexAccessedPath iap; while (true) { - path.push_back(parseIdentifier()); + iap.path.push_back(parseIdentifier()); if (m_scanner->currentToken() != Token::Period) break; m_scanner->next(); }; - auto eventName = expressionFromIndexAccessStructure(path, {}); + auto eventName = expressionFromIndexAccessStructure(iap); expectToken(Token::LParen); vector<ASTPointer<Expression>> arguments; @@ -1098,46 +1098,17 @@ ASTPointer<Statement> Parser::parseSimpleStatement(ASTPointer<ASTString> const& default: break; } + // At this point, we have 'Identifier "["' or 'Identifier "." Identifier' or 'ElementoryTypeName "["'. - // We parse '(Identifier ("." Identifier)* |ElementaryTypeName) ( "[" Expression "]" )+' + // We parse '(Identifier ("." Identifier)* |ElementaryTypeName) ( "[" Expression "]" )*' // until we can decide whether to hand this over to ExpressionStatement or create a // VariableDeclarationStatement out of it. - vector<ASTPointer<PrimaryExpression>> path; - bool startedWithElementary = false; - if (m_scanner->currentToken() == Token::Identifier) - path.push_back(parseIdentifier()); - else - { - startedWithElementary = true; - unsigned firstNum; - unsigned secondNum; - tie(firstNum, secondNum) = m_scanner->currentTokenInfo(); - ElementaryTypeNameToken elemToken(m_scanner->currentToken(), firstNum, secondNum); - path.push_back(ASTNodeFactory(*this).createNode<ElementaryTypeNameExpression>(elemToken)); - m_scanner->next(); - } - while (!startedWithElementary && m_scanner->currentToken() == Token::Period) - { - m_scanner->next(); - path.push_back(parseIdentifier()); - } - vector<pair<ASTPointer<Expression>, SourceLocation>> indices; - while (m_scanner->currentToken() == Token::LBrack) - { - expectToken(Token::LBrack); - ASTPointer<Expression> index; - if (m_scanner->currentToken() != Token::RBrack) - index = parseExpression(); - SourceLocation indexLocation = path.front()->location(); - indexLocation.end = endPosition(); - indices.push_back(make_pair(index, indexLocation)); - expectToken(Token::RBrack); - } + IndexAccessedPath iap = parseIndexAccessedPath(); if (m_scanner->currentToken() == Token::Identifier || Token::isLocationSpecifier(m_scanner->currentToken())) - return parseVariableDeclarationStatement(_docString, typeNameIndexAccessStructure(path, indices)); + return parseVariableDeclarationStatement(_docString, typeNameFromIndexAccessStructure(iap)); else - return parseExpressionStatement(_docString, expressionFromIndexAccessStructure(path, indices)); + return parseExpressionStatement(_docString, expressionFromIndexAccessStructure(iap)); } ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStatement( @@ -1529,32 +1500,65 @@ Parser::LookAheadInfo Parser::peekStatementType() const return LookAheadInfo::ExpressionStatement; } -ASTPointer<TypeName> Parser::typeNameIndexAccessStructure( - vector<ASTPointer<PrimaryExpression>> const& _path, - vector<pair<ASTPointer<Expression>, SourceLocation>> const& _indices -) +Parser::IndexAccessedPath Parser::parseIndexAccessedPath() +{ + IndexAccessedPath iap; + if (m_scanner->currentToken() == Token::Identifier) + { + iap.path.push_back(parseIdentifier()); + while (m_scanner->currentToken() == Token::Period) + { + m_scanner->next(); + iap.path.push_back(parseIdentifier()); + } + } + else + { + unsigned firstNum; + unsigned secondNum; + tie(firstNum, secondNum) = m_scanner->currentTokenInfo(); + ElementaryTypeNameToken elemToken(m_scanner->currentToken(), firstNum, secondNum); + iap.path.push_back(ASTNodeFactory(*this).createNode<ElementaryTypeNameExpression>(elemToken)); + m_scanner->next(); + } + while (m_scanner->currentToken() == Token::LBrack) + { + expectToken(Token::LBrack); + ASTPointer<Expression> index; + if (m_scanner->currentToken() != Token::RBrack) + index = parseExpression(); + SourceLocation indexLocation = iap.path.front()->location(); + indexLocation.end = endPosition(); + iap.indices.push_back(make_pair(index, indexLocation)); + expectToken(Token::RBrack); + } + + return iap; +} + +ASTPointer<TypeName> Parser::typeNameFromIndexAccessStructure(Parser::IndexAccessedPath const& _iap) { - solAssert(!_path.empty(), ""); + solAssert(!_iap.path.empty(), ""); RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); - SourceLocation location = _path.front()->location(); - location.end = _path.back()->location().end; + SourceLocation location = _iap.path.front()->location(); + location.end = _iap.path.back()->location().end; nodeFactory.setLocation(location); ASTPointer<TypeName> type; - if (auto typeName = dynamic_cast<ElementaryTypeNameExpression const*>(_path.front().get())) + if (auto typeName = dynamic_cast<ElementaryTypeNameExpression const*>(_iap.path.front().get())) { - solAssert(_path.size() == 1, ""); + solAssert(_iap.path.size() == 1, ""); type = nodeFactory.createNode<ElementaryTypeName>(typeName->typeName()); } else { vector<ASTString> path; - for (auto const& el: _path) + for (auto const& el: _iap.path) path.push_back(dynamic_cast<Identifier const&>(*el).name()); type = nodeFactory.createNode<UserDefinedTypeName>(path); } - for (auto const& lengthExpression: _indices) + for (auto const& lengthExpression: _iap.indices) { nodeFactory.setLocation(lengthExpression.second); type = nodeFactory.createNode<ArrayTypeName>(type, lengthExpression.first); @@ -1563,26 +1567,25 @@ ASTPointer<TypeName> Parser::typeNameIndexAccessStructure( } ASTPointer<Expression> Parser::expressionFromIndexAccessStructure( - vector<ASTPointer<PrimaryExpression>> const& _path, - vector<pair<ASTPointer<Expression>, SourceLocation>> const& _indices + Parser::IndexAccessedPath const& _iap ) { - solAssert(!_path.empty(), ""); + solAssert(!_iap.path.empty(), ""); RecursionGuard recursionGuard(*this); - ASTNodeFactory nodeFactory(*this, _path.front()); - ASTPointer<Expression> expression(_path.front()); - for (size_t i = 1; i < _path.size(); ++i) + ASTNodeFactory nodeFactory(*this, _iap.path.front()); + ASTPointer<Expression> expression(_iap.path.front()); + for (size_t i = 1; i < _iap.path.size(); ++i) { - SourceLocation location(_path.front()->location()); - location.end = _path[i]->location().end; + SourceLocation location(_iap.path.front()->location()); + location.end = _iap.path[i]->location().end; nodeFactory.setLocation(location); - Identifier const& identifier = dynamic_cast<Identifier const&>(*_path[i]); + Identifier const& identifier = dynamic_cast<Identifier const&>(*_iap.path[i]); expression = nodeFactory.createNode<MemberAccess>( expression, make_shared<ASTString>(identifier.name()) ); } - for (auto const& index: _indices) + for (auto const& index: _iap.indices) { nodeFactory.setLocation(index.second); expression = nodeFactory.createNode<IndexAccess>(expression, index.first); diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index eb120a61..c4254231 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -145,21 +145,25 @@ private: { IndexAccessStructure, VariableDeclarationStatement, ExpressionStatement }; + /// Structure that represents a.b.c[x][y][z]. Can be converted either to an expression + /// or to a type name. Path cannot be empty, but indices can be empty. + struct IndexAccessedPath + { + std::vector<ASTPointer<PrimaryExpression>> path; + std::vector<std::pair<ASTPointer<Expression>, SourceLocation>> indices; + }; /// Performs limited look-ahead to distinguish between variable declaration and expression statement. /// For source code of the form "a[][8]" ("IndexAccessStructure"), this is not possible to /// decide with constant look-ahead. LookAheadInfo peekStatementType() const; + /// @returns an IndexAccessedPath as a prestage to parsing a variable declaration (type name) + /// or an expression; + IndexAccessedPath parseIndexAccessedPath(); /// @returns a typename parsed in look-ahead fashion from something like "a.b[8][2**70]". - ASTPointer<TypeName> typeNameIndexAccessStructure( - std::vector<ASTPointer<PrimaryExpression>> const& _path, - std::vector<std::pair<ASTPointer<Expression>, SourceLocation>> const& _indices - ); + ASTPointer<TypeName> typeNameFromIndexAccessStructure(IndexAccessedPath const& _pathAndIndices); /// @returns an expression parsed in look-ahead fashion from something like "a.b[8][2**70]". - ASTPointer<Expression> expressionFromIndexAccessStructure( - std::vector<ASTPointer<PrimaryExpression>> const& _path, - std::vector<std::pair<ASTPointer<Expression>, SourceLocation>> const& _indices - ); + ASTPointer<Expression> expressionFromIndexAccessStructure(IndexAccessedPath const& _pathAndIndices); std::string currentTokenName(); Token::Value expectAssignmentOperator(); diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index ee8da322..4525cbf9 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -53,6 +53,7 @@ class ExecutionFramework public: ExecutionFramework(); + virtual ~ExecutionFramework() = default; virtual bytes const& compileAndRunWithoutCheck( std::string const& _sourceCode, diff --git a/test/libsolidity/AnalysisFramework.h b/test/libsolidity/AnalysisFramework.h index 05490a42..a904617d 100644 --- a/test/libsolidity/AnalysisFramework.h +++ b/test/libsolidity/AnalysisFramework.h @@ -52,6 +52,7 @@ protected: bool _insertVersionPragma = true, bool _allowMultipleErrors = false ); + virtual ~AnalysisFramework() = default; SourceUnit const* parseAndAnalyse(std::string const& _source); bool success(std::string const& _source); diff --git a/test/libsolidity/syntaxTests/empty_string_var.sol b/test/libsolidity/syntaxTests/empty_string_var.sol new file mode 100644 index 00000000..e9837590 --- /dev/null +++ b/test/libsolidity/syntaxTests/empty_string_var.sol @@ -0,0 +1,11 @@ +contract C { + function f() { + var a = ""; + bytes1 b = bytes1(a); + bytes memory c = bytes(a); + string memory d = string(a); + } +} +// ---- +// Warning: (34-39): Use of the "var" keyword is deprecated. +// TypeError: (61-70): Explicit type conversion not allowed from "string memory" to "bytes1". diff --git a/test/libsolidity/syntaxTests/types/bytes0.sol b/test/libsolidity/syntaxTests/types/bytes0.sol new file mode 100644 index 00000000..7c6d5974 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/bytes0.sol @@ -0,0 +1,5 @@ +contract C { + bytes0 b0 = 1; +} +// ---- +// DeclarationError: (15-21): Identifier not found or not unique. diff --git a/test/libsolidity/syntaxTests/types/bytes256.sol b/test/libsolidity/syntaxTests/types/bytes256.sol new file mode 100644 index 00000000..22b5408d --- /dev/null +++ b/test/libsolidity/syntaxTests/types/bytes256.sol @@ -0,0 +1,5 @@ +contract C { + bytes256 b256 = 1; +} +// ---- +// DeclarationError: (15-23): Identifier not found or not unique. diff --git a/test/libsolidity/syntaxTests/types/bytes33.sol b/test/libsolidity/syntaxTests/types/bytes33.sol new file mode 100644 index 00000000..7edf13d3 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/bytes33.sol @@ -0,0 +1,5 @@ +contract C { + bytes33 b33 = 1; +} +// ---- +// DeclarationError: (15-22): Identifier not found or not unique. diff --git a/test/libsolidity/syntaxTests/types/bytesm.sol b/test/libsolidity/syntaxTests/types/bytesm.sol new file mode 100644 index 00000000..550760b9 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/bytesm.sol @@ -0,0 +1,36 @@ +contract C { + byte b = byte(1); + bytes1 b1 = b; + bytes2 b2 = b1; + bytes3 b3 = b2; + bytes4 b4 = b3; + bytes5 b5 = b4; + bytes6 b6 = b5; + bytes7 b7 = b6; + bytes8 b8 = b7; + bytes9 b9 = b8; + bytes10 b10 = b9; + bytes11 b11 = b10; + bytes12 b12 = b11; + bytes13 b13 = b12; + bytes14 b14 = b13; + bytes15 b15 = b14; + bytes16 b16 = b15; + bytes17 b17 = b16; + bytes18 b18 = b17; + bytes19 b19 = b18; + bytes20 b20 = b19; + bytes21 b21 = b20; + bytes22 b22 = b21; + bytes23 b23 = b22; + bytes24 b24 = b23; + bytes25 b25 = b24; + bytes26 b26 = b25; + bytes27 b27 = b26; + bytes28 b28 = b27; + bytes29 b29 = b28; + bytes30 b30 = b29; + bytes31 b31 = b30; + bytes32 b32 = b31; +} +// ---- |