diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 1247f325a..d171006a8 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -174,7 +174,15 @@ void VariableDefinition::checkTypeRequirements() { // no type declared and no previous assignment, infer the type m_value->checkTypeRequirements(); - m_variable->setType(m_value->getType()); + TypePointer type = m_value->getType(); + if (type->getCategory() == Type::Category::INTEGER_CONSTANT) + { + auto intType = dynamic_pointer_cast(type)->getIntegerType(); + if (!intType) + BOOST_THROW_EXCEPTION(m_value->createTypeError("Invalid integer constant " + type->toString())); + type = intType; + } + m_variable->setType(type); } } } @@ -193,16 +201,22 @@ void Assignment::checkTypeRequirements() { // compound assignment m_rightHandSide->checkTypeRequirements(); - TypePointer resultType = Type::binaryOperatorResult(Token::AssignmentToBinaryOp(m_assigmentOperator), - m_type, m_rightHandSide->getType()); + TypePointer resultType = m_type->binaryOperatorResult(Token::AssignmentToBinaryOp(m_assigmentOperator), + m_rightHandSide->getType()); if (!resultType || *resultType != *m_type) - BOOST_THROW_EXCEPTION(createTypeError("Operator not compatible with type.")); + BOOST_THROW_EXCEPTION(createTypeError("Operator " + string(Token::toString(m_assigmentOperator)) + + " not compatible with types " + + m_type->toString() + " and " + + m_rightHandSide->getType()->toString())); } } void ExpressionStatement::checkTypeRequirements() { m_expression->checkTypeRequirements(); + if (m_expression->getType()->getCategory() == Type::Category::INTEGER_CONSTANT) + if (!dynamic_pointer_cast(m_expression->getType())->getIntegerType()) + BOOST_THROW_EXCEPTION(m_expression->createTypeError("Invalid integer constant.")); } void Expression::expectType(Type const& _expectedType) @@ -228,8 +242,8 @@ void UnaryOperation::checkTypeRequirements() m_subExpression->checkTypeRequirements(); if (m_operator == Token::Value::INC || m_operator == Token::Value::DEC || m_operator == Token::Value::DELETE) m_subExpression->requireLValue(); - m_type = m_subExpression->getType(); - if (!m_type->acceptsUnaryOperator(m_operator)) + m_type = m_subExpression->getType()->unaryOperatorResult(m_operator); + if (!m_type) BOOST_THROW_EXCEPTION(createTypeError("Unary operator not compatible with type.")); } @@ -237,7 +251,7 @@ void BinaryOperation::checkTypeRequirements() { m_left->checkTypeRequirements(); m_right->checkTypeRequirements(); - m_commonType = Type::binaryOperatorResult(m_operator, m_left->getType(), m_right->getType()); + m_commonType = m_left->getType()->binaryOperatorResult(m_operator, m_right->getType()); if (!m_commonType) BOOST_THROW_EXCEPTION(createTypeError("Operator " + string(Token::toString(m_operator)) + " not compatible with types " + @@ -301,7 +315,7 @@ void NewExpression::checkTypeRequirements() m_contract = dynamic_cast(m_contractName->getReferencedDeclaration()); if (!m_contract) BOOST_THROW_EXCEPTION(createTypeError("Identifier is not a contract.")); - shared_ptr type = make_shared(*m_contract); + shared_ptr type = make_shared(*m_contract); m_type = type; TypePointers const& parameterTypes = type->getConstructorType()->getParameterTypes(); if (parameterTypes.size() != m_arguments.size()) @@ -352,7 +366,7 @@ void Identifier::checkTypeRequirements() if (structDef) { // note that we do not have a struct type here - m_type = make_shared(make_shared(*structDef)); + m_type = make_shared(make_shared(*structDef)); return; } FunctionDefinition const* functionDef = dynamic_cast(m_referencedDeclaration); @@ -361,13 +375,13 @@ void Identifier::checkTypeRequirements() // a function reference is not a TypeType, because calling a TypeType converts to the type. // Calling a function (e.g. function(12), otherContract.function(34)) does not do a type // conversion. - m_type = make_shared(*functionDef); + m_type = make_shared(*functionDef); return; } ContractDefinition const* contractDef = dynamic_cast(m_referencedDeclaration); if (contractDef) { - m_type = make_shared(make_shared(*contractDef)); + m_type = make_shared(make_shared(*contractDef)); return; } MagicVariableDeclaration const* magicVariable = dynamic_cast(m_referencedDeclaration); @@ -381,14 +395,14 @@ void Identifier::checkTypeRequirements() void ElementaryTypeNameExpression::checkTypeRequirements() { - m_type = make_shared(Type::fromElementaryTypeName(m_typeToken)); + m_type = make_shared(Type::fromElementaryTypeName(m_typeToken)); } void Literal::checkTypeRequirements() { m_type = Type::forLiteral(*this); if (!m_type) - BOOST_THROW_EXCEPTION(createTypeError("Literal value too large.")); + BOOST_THROW_EXCEPTION(createTypeError("Invalid literal value.")); } } diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 7238a6a1b..cf91a7630 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -71,12 +71,20 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) return false; } -void ExpressionCompiler::endVisit(UnaryOperation const& _unaryOperation) +bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) { //@todo type checking and creating code for an operator should be in the same place: // the operator should know how to convert itself and to which types it applies, so // put this code together with "Type::acceptsBinary/UnaryOperator" into a class that // represents the operator + if (_unaryOperation.getType()->getCategory() == Type::Category::INTEGER_CONSTANT) + { + m_context << _unaryOperation.getType()->literalValue(nullptr); + return false; + } + + _unaryOperation.getSubExpression().accept(*this); + switch (_unaryOperation.getOperator()) { case Token::NOT: // ! @@ -128,6 +136,7 @@ void ExpressionCompiler::endVisit(UnaryOperation const& _unaryOperation) BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid unary operator: " + string(Token::toString(_unaryOperation.getOperator())))); } + return false; } bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) @@ -139,17 +148,19 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) if (op == Token::AND || op == Token::OR) // special case: short-circuiting appendAndOrOperatorCode(_binaryOperation); + else if (commonType.getCategory() == Type::Category::INTEGER_CONSTANT) + m_context << commonType.literalValue(nullptr); else { - bool cleanupNeeded = false; - if (commonType.getCategory() == Type::Category::INTEGER) - if (Token::isCompareOp(op) || op == Token::DIV || op == Token::MOD) - cleanupNeeded = true; + bool cleanupNeeded = commonType.getCategory() == Type::Category::INTEGER && + (Token::isCompareOp(op) || op == Token::DIV || op == Token::MOD); // for commutative operators, push the literal as late as possible to allow improved optimization - //@todo this has to be extended for literal expressions - bool swap = (m_optimize && Token::isCommutativeOp(op) && dynamic_cast(&rightExpression) - && !dynamic_cast(&leftExpression)); + auto isLiteral = [](Expression const& _e) + { + return dynamic_cast(&_e) || _e.getType()->getCategory() == Type::Category::INTEGER_CONSTANT; + }; + bool swap = m_optimize && Token::isCommutativeOp(op) && isLiteral(rightExpression) && !isLiteral(leftExpression); if (swap) { leftExpression.accept(*this); @@ -478,10 +489,10 @@ void ExpressionCompiler::endVisit(Literal const& _literal) { switch (_literal.getType()->getCategory()) { - case Type::Category::INTEGER: + case Type::Category::INTEGER_CONSTANT: case Type::Category::BOOL: case Type::Category::STRING: - m_context << _literal.getType()->literalValue(_literal); + m_context << _literal.getType()->literalValue(&_literal); break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Only integer, boolean and string literals implemented for now.")); @@ -617,9 +628,16 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con if (_typeOnStack == _targetType && !_cleanupNeeded) return; - if (_typeOnStack.getCategory() == Type::Category::INTEGER) + Type::Category stackTypeCategory = _typeOnStack.getCategory(); + Type::Category targetTypeCategory = _targetType.getCategory(); + if (stackTypeCategory == Type::Category::INTEGER) + { + solAssert(targetTypeCategory == Type::Category::INTEGER || targetTypeCategory == Type::Category::CONTRACT, ""); appendHighBitsCleanup(dynamic_cast(_typeOnStack)); - else if (_typeOnStack.getCategory() == Type::Category::STRING) + } + else if (stackTypeCategory == Type::Category::INTEGER_CONSTANT) + solAssert(targetTypeCategory == Type::Category::INTEGER || targetTypeCategory == Type::Category::CONTRACT, ""); + else if (stackTypeCategory == Type::Category::STRING) { // nothing to do, strings are high-order-bit-aligned //@todo clear lower-order bytes if we allow explicit conversion to shorter strings diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index e39bcddb2..abd7f7750 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -58,7 +58,7 @@ private: m_optimize(_optimize), m_context(_compilerContext), m_currentLValue(m_context) {} virtual bool visit(Assignment const& _assignment) override; - virtual void endVisit(UnaryOperation const& _unaryOperation) override; + virtual bool visit(UnaryOperation const& _unaryOperation) override; virtual bool visit(BinaryOperation const& _binaryOperation) override; virtual bool visit(FunctionCall const& _functionCall) override; virtual bool visit(NewExpression const& _newExpression) override; diff --git a/libsolidity/Scanner.cpp b/libsolidity/Scanner.cpp index 6433b5262..6e3d04bc5 100644 --- a/libsolidity/Scanner.cpp +++ b/libsolidity/Scanner.cpp @@ -455,7 +455,7 @@ void Scanner::scanToken() token = Token::ADD; break; case '-': - // - -- -= Number + // - -- -= advance(); if (m_char == '-') { @@ -464,8 +464,6 @@ void Scanner::scanToken() } else if (m_char == '=') token = selectToken(Token::ASSIGN_SUB); - else if (m_char == '.' || isDecimalDigit(m_char)) - token = scanNumber('-'); else token = Token::SUB; break; @@ -650,8 +648,7 @@ Token::Value Scanner::scanNumber(char _charSeen) } else { - if (_charSeen == '-') - addLiteralChar('-'); + solAssert(_charSeen == 0, ""); // if the first character is '0' we must check for octals and hex if (m_char == '0') { diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 01fa77341..da042edb4 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -44,17 +44,17 @@ shared_ptr Type::fromElementaryTypeName(Token::Value _typeToken) if (bytes == 0) bytes = 32; int modifier = offset / 33; - return make_shared(bytes * 8, + return make_shared(bytes * 8, modifier == 0 ? IntegerType::Modifier::SIGNED : modifier == 1 ? IntegerType::Modifier::UNSIGNED : IntegerType::Modifier::HASH); } else if (_typeToken == Token::ADDRESS) - return make_shared(0, IntegerType::Modifier::ADDRESS); + return make_shared(0, IntegerType::Modifier::ADDRESS); else if (_typeToken == Token::BOOL) - return make_shared(); + return make_shared(); else if (Token::STRING0 <= _typeToken && _typeToken <= Token::STRING32) - return make_shared(int(_typeToken) - int(Token::STRING0)); + return make_shared(int(_typeToken) - int(Token::STRING0)); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to convert elementary typename " + std::string(Token::toString(_typeToken)) + " to type.")); @@ -64,11 +64,11 @@ shared_ptr Type::fromUserDefinedTypeName(UserDefinedTypeName const& { Declaration const* declaration = _typeName.getReferencedDeclaration(); if (StructDefinition const* structDef = dynamic_cast(declaration)) - return make_shared(*structDef); + return make_shared(*structDef); else if (FunctionDefinition const* function = dynamic_cast(declaration)) - return make_shared(*function); + return make_shared(*function); else if (ContractDefinition const* contract = dynamic_cast(declaration)) - return make_shared(*contract); + return make_shared(*contract); return shared_ptr(); } @@ -80,7 +80,7 @@ shared_ptr Type::fromMapping(Mapping const& _typeName) shared_ptr valueType = _typeName.getValueType().toType(); if (!valueType) BOOST_THROW_EXCEPTION(_typeName.getValueType().createTypeError("Invalid type name")); - return make_shared(keyType, valueType); + return make_shared(keyType, valueType); } shared_ptr Type::forLiteral(Literal const& _literal) @@ -89,14 +89,14 @@ shared_ptr Type::forLiteral(Literal const& _literal) { case Token::TRUE_LITERAL: case Token::FALSE_LITERAL: - return make_shared(); + return make_shared(); case Token::NUMBER: - return IntegerType::smallestTypeForLiteral(_literal.getValue()); + return IntegerConstantType::fromLiteral(_literal.getValue()); case Token::STRING_LITERAL: //@todo put larger strings into dynamic strings return StaticStringType::smallestTypeForLiteral(_literal.getValue()); default: - return shared_ptr(); + return shared_ptr(); } } @@ -112,19 +112,6 @@ TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b) const MemberList Type::EmptyMemberList = MemberList(); -shared_ptr IntegerType::smallestTypeForLiteral(string const& _literal) -{ - bigint value(_literal); - bool isSigned = value < 0 || (!_literal.empty() && _literal.front() == '-'); - if (isSigned) - // convert to positive number of same bit requirements - value = ((-value) - 1) << 1; - unsigned bytes = max(bytesRequired(value), 1u); - if (bytes > 32) - return shared_ptr(); - return make_shared(bytes * 8, isSigned ? Modifier::SIGNED : Modifier::UNSIGNED); -} - IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): m_bits(_bits), m_modifier(_modifier) { @@ -156,18 +143,26 @@ bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const return _convertTo.getCategory() == getCategory() || _convertTo.getCategory() == Category::CONTRACT; } -bool IntegerType::acceptsUnaryOperator(Token::Value _operator) const +TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const { + // "delete" is ok for all integer types if (_operator == Token::DELETE) - return true; - if (isAddress()) - return false; - if (_operator == Token::BIT_NOT) - return true; - if (isHash()) - return false; - return _operator == Token::ADD || _operator == Token::SUB || - _operator == Token::INC || _operator == Token::DEC; + return shared_from_this(); + // no further unary operators for addresses + else if (isAddress()) + return TypePointer(); + // "~" is ok for all other types + else if (_operator == Token::BIT_NOT) + return shared_from_this(); + // nothing else for hashes + else if (isHash()) + return TypePointer(); + // for non-hash integers, we allow +, -, ++ and -- + else if (_operator == Token::ADD || _operator == Token::SUB || + _operator == Token::INC || _operator == Token::DEC) + return shared_from_this(); + else + return TypePointer(); } bool IntegerType::operator==(Type const& _other) const @@ -186,17 +181,11 @@ string IntegerType::toString() const return prefix + dev::toString(m_bits); } -u256 IntegerType::literalValue(Literal const& _literal) const -{ - bigint value(_literal.getValue()); - return u256(value); -} - -TypePointer IntegerType::binaryOperatorResultImpl(Token::Value _operator, TypePointer const& _this, TypePointer const& _other) const +TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const { - if (getCategory() != _other->getCategory()) + if (_other->getCategory() != Category::INTEGER_CONSTANT && _other->getCategory() != getCategory()) return TypePointer(); - auto commonType = dynamic_pointer_cast(Type::commonType(_this, _other)); + auto commonType = dynamic_pointer_cast(Type::commonType(shared_from_this(), _other)); if (!commonType) return TypePointer(); @@ -216,17 +205,152 @@ TypePointer IntegerType::binaryOperatorResultImpl(Token::Value _operator, TypePo const MemberList IntegerType::AddressMemberList = MemberList({{"balance", - make_shared(256)}, + make_shared(256)}, {"callstring32", - make_shared(TypePointers({make_shared(32)}), - TypePointers(), FunctionType::Location::BARE)}, + make_shared(TypePointers({make_shared(32)}), + TypePointers(), FunctionType::Location::BARE)}, {"callstring32string32", - make_shared(TypePointers({make_shared(32), - make_shared(32)}), - TypePointers(), FunctionType::Location::BARE)}, + make_shared(TypePointers({make_shared(32), + make_shared(32)}), + TypePointers(), FunctionType::Location::BARE)}, {"send", - make_shared(TypePointers({make_shared(256)}), - TypePointers(), FunctionType::Location::SEND)}}); + make_shared(TypePointers({make_shared(256)}), + TypePointers(), FunctionType::Location::SEND)}}); + +shared_ptr IntegerConstantType::fromLiteral(string const& _literal) +{ + return make_shared(bigint(_literal)); +} + +bool IntegerConstantType::isImplicitlyConvertibleTo(Type const& _convertTo) const +{ + TypePointer integerType = getIntegerType(); + return integerType && integerType->isImplicitlyConvertibleTo(_convertTo); +} + +bool IntegerConstantType::isExplicitlyConvertibleTo(Type const& _convertTo) const +{ + TypePointer integerType = getIntegerType(); + return integerType && integerType->isExplicitlyConvertibleTo(_convertTo); +} + +TypePointer IntegerConstantType::unaryOperatorResult(Token::Value _operator) const +{ + bigint value; + switch (_operator) + { + case Token::BIT_NOT: + value = ~m_value; + break; + case Token::ADD: + value = m_value; + break; + case Token::SUB: + value = -m_value; + break; + default: + return TypePointer(); + } + return make_shared(value); +} + +TypePointer IntegerConstantType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const +{ + if (_other->getCategory() == Category::INTEGER) + { + shared_ptr integerType = getIntegerType(); + if (!integerType) + return TypePointer(); + return integerType->binaryOperatorResult(_operator, _other); + } + else if (_other->getCategory() != getCategory()) + return TypePointer(); + + IntegerConstantType const& other = dynamic_cast(*_other); + if (Token::isCompareOp(_operator)) + { + shared_ptr thisIntegerType = getIntegerType(); + shared_ptr otherIntegerType = other.getIntegerType(); + if (!thisIntegerType || !otherIntegerType) + return TypePointer(); + return thisIntegerType->binaryOperatorResult(_operator, otherIntegerType); + } + else + { + bigint value; + switch (_operator) + { + case Token::BIT_OR: + value = m_value | other.m_value; + break; + case Token::BIT_XOR: + value = m_value ^ other.m_value; + break; + case Token::BIT_AND: + value = m_value & other.m_value; + break; + case Token::ADD: + value = m_value + other.m_value; + break; + case Token::SUB: + value = m_value - other.m_value; + break; + case Token::MUL: + value = m_value * other.m_value; + break; + case Token::DIV: + if (other.m_value == 0) + return TypePointer(); + value = m_value / other.m_value; + break; + case Token::MOD: + if (other.m_value == 0) + return TypePointer(); + value = m_value % other.m_value; + break; + default: + return TypePointer(); + } + return make_shared(value); + } +} + +bool IntegerConstantType::operator==(Type const& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + return m_value == dynamic_cast(_other).m_value; +} + +string IntegerConstantType::toString() const +{ + return "int_const " + m_value.str(); +} + +u256 IntegerConstantType::literalValue(Literal const*) const +{ + // we ignore the literal and hope that the type was correctly determined + solAssert(m_value <= u256(-1), "Integer constant too large."); + solAssert(m_value >= -(bigint(1) << 255), "Integer constant too small."); + if (m_value >= 0) + return u256(m_value); + else + return s2u(s256(m_value)); +} + +shared_ptr IntegerConstantType::getIntegerType() const +{ + bigint value = m_value; + bool negative = (value < 0); + if (negative) // convert to positive number of same bit requirements + value = ((-value) - 1) << 1; + if (value > u256(-1)) + return shared_ptr(); + else + return make_shared(max(bytesRequired(value), 1u) * 8, + negative ? IntegerType::Modifier::SIGNED + : IntegerType::Modifier::UNSIGNED); +} shared_ptr StaticStringType::smallestTypeForLiteral(string const& _literal) { @@ -257,12 +381,13 @@ bool StaticStringType::operator==(Type const& _other) const return other.m_bytes == m_bytes; } -u256 StaticStringType::literalValue(const Literal& _literal) const +u256 StaticStringType::literalValue(const Literal* _literal) const { + solAssert(_literal, ""); u256 value = 0; - for (char c: _literal.getValue()) + for (char c: _literal->getValue()) value = (value << 8) | byte(c); - return value << ((32 - _literal.getValue().length()) * 8); + return value << ((32 - _literal->getValue().length()) * 8); } bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const @@ -278,22 +403,23 @@ bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const return isImplicitlyConvertibleTo(_convertTo); } -u256 BoolType::literalValue(Literal const& _literal) const +u256 BoolType::literalValue(Literal const* _literal) const { - if (_literal.getToken() == Token::TRUE_LITERAL) + solAssert(_literal, ""); + if (_literal->getToken() == Token::TRUE_LITERAL) return u256(1); - else if (_literal.getToken() == Token::FALSE_LITERAL) + else if (_literal->getToken() == Token::FALSE_LITERAL) return u256(0); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Bool type constructed from non-boolean literal.")); } -TypePointer BoolType::binaryOperatorResultImpl(Token::Value _operator, TypePointer const& _this, TypePointer const& _other) const +TypePointer BoolType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const { if (getCategory() != _other->getCategory()) return TypePointer(); if (Token::isCompareOp(_operator) || _operator == Token::AND || _operator == Token::OR) - return _this; + return _other; else return TypePointer(); } @@ -347,9 +473,9 @@ shared_ptr const& ContractType::getConstructorType() const { FunctionDefinition const* constructor = m_contract.getConstructor(); if (constructor) - m_constructorType = make_shared(*constructor); + m_constructorType = make_shared(*constructor); else - m_constructorType = make_shared(TypePointers(), TypePointers()); + m_constructorType = make_shared(TypePointers(), TypePointers()); } return m_constructorType; } @@ -519,21 +645,21 @@ MagicType::MagicType(MagicType::Kind _kind): switch (m_kind) { case Kind::BLOCK: - m_members = MemberList({{"coinbase", make_shared(0, IntegerType::Modifier::ADDRESS)}, - {"timestamp", make_shared(256)}, - {"prevhash", make_shared(256, IntegerType::Modifier::HASH)}, - {"difficulty", make_shared(256)}, - {"number", make_shared(256)}, - {"gaslimit", make_shared(256)}}); + m_members = MemberList({{"coinbase", make_shared(0, IntegerType::Modifier::ADDRESS)}, + {"timestamp", make_shared(256)}, + {"prevhash", make_shared(256, IntegerType::Modifier::HASH)}, + {"difficulty", make_shared(256)}, + {"number", make_shared(256)}, + {"gaslimit", make_shared(256)}}); break; case Kind::MSG: - m_members = MemberList({{"sender", make_shared(0, IntegerType::Modifier::ADDRESS)}, - {"gas", make_shared(256)}, - {"value", make_shared(256)}}); + m_members = MemberList({{"sender", make_shared(0, IntegerType::Modifier::ADDRESS)}, + {"gas", make_shared(256)}, + {"value", make_shared(256)}}); break; case Kind::TX: - m_members = MemberList({{"origin", make_shared(0, IntegerType::Modifier::ADDRESS)}, - {"gasprice", make_shared(256)}}); + m_members = MemberList({{"origin", make_shared(0, IntegerType::Modifier::ADDRESS)}, + {"gasprice", make_shared(256)}}); break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of magic.")); diff --git a/libsolidity/Types.h b/libsolidity/Types.h index e50cbc0a8..8c800fa0d 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -70,12 +70,12 @@ private: /** * Abstract base class that forms the root of the type hierarchy. */ -class Type: private boost::noncopyable +class Type: private boost::noncopyable, public std::enable_shared_from_this { public: enum class Category { - INTEGER, BOOL, REAL, STRING, CONTRACT, STRUCT, FUNCTION, MAPPING, VOID, TYPE, MAGIC + INTEGER, INTEGER_CONSTANT, BOOL, REAL, STRING, CONTRACT, STRUCT, FUNCTION, MAPPING, VOID, TYPE, MAGIC }; ///@{ @@ -92,12 +92,6 @@ public: static TypePointer forLiteral(Literal const& _literal); /// @returns a pointer to _a or _b if the other is implicitly convertible to it or nullptr otherwise static TypePointer commonType(TypePointer const& _a, TypePointer const& _b); - /// @returns the resulting type of applying the given operator or an empty pointer if this is not possible. - /// The default implementation allows comparison operators if a common type exists - static TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _a, TypePointer const& _b) - { - return _a->binaryOperatorResultImpl(_operator, _a, _b); - } virtual Category getCategory() const = 0; virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } @@ -105,7 +99,17 @@ public: { return isImplicitlyConvertibleTo(_convertTo); } - virtual bool acceptsUnaryOperator(Token::Value) const { return false; } + /// @returns the resulting type of applying the given unary operator or an empty pointer if + /// this is not possible. + /// The default implementation does not allow any unary operator. + virtual TypePointer unaryOperatorResult(Token::Value) const { return TypePointer(); } + /// @returns the resulting type of applying the given binary operator or an empty pointer if + /// this is not possible. + /// The default implementation allows comparison operators if a common type exists + virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const + { + return Token::isCompareOp(_operator) ? commonType(shared_from_this(), _other) : TypePointer(); + } virtual bool operator==(Type const& _other) const { return getCategory() == _other.getCategory(); } virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); } @@ -131,18 +135,13 @@ public: TypePointer getMemberType(std::string const& _name) const { return getMembers().getMemberType(_name); } virtual std::string toString() const = 0; - virtual u256 literalValue(Literal const&) const + virtual u256 literalValue(Literal const*) const { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Literal value requested " "for type without literals.")); } protected: - virtual TypePointer binaryOperatorResultImpl(Token::Value _operator, TypePointer const& _a, TypePointer const& _b) const - { - return Token::isCompareOp(_operator) ? commonType(_a, _b) : TypePointer(); - } - /// Convenience object used when returning an empty member list. static const MemberList EmptyMemberList; }; @@ -159,15 +158,12 @@ public: }; virtual Category getCategory() const override { return Category::INTEGER; } - /// @returns the smallest integer type for the given literal or an empty pointer - /// if no type fits. - static std::shared_ptr smallestTypeForLiteral(std::string const& _literal); - explicit IntegerType(int _bits, Modifier _modifier = Modifier::UNSIGNED); virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual bool acceptsUnaryOperator(Token::Value _operator) const override; + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; virtual bool operator==(Type const& _other) const override; @@ -177,22 +173,52 @@ public: virtual MemberList const& getMembers() const { return isAddress() ? AddressMemberList : EmptyMemberList; } virtual std::string toString() const override; - virtual u256 literalValue(Literal const& _literal) const override; int getNumBits() const { return m_bits; } bool isHash() const { return m_modifier == Modifier::HASH || m_modifier == Modifier::ADDRESS; } bool isAddress() const { return m_modifier == Modifier::ADDRESS; } int isSigned() const { return m_modifier == Modifier::SIGNED; } -protected: - virtual TypePointer binaryOperatorResultImpl(Token::Value _operator, TypePointer const& _this, TypePointer const& _other) const override; - private: int m_bits; Modifier m_modifier; static const MemberList AddressMemberList; }; +/** + * Integer constants either literals or computed. Example expressions: 2, 2+10, ~10. + * There is one distinct type per value. + */ +class IntegerConstantType: public Type +{ +public: + virtual Category getCategory() const override { return Category::INTEGER_CONSTANT; } + + static std::shared_ptr fromLiteral(std::string const& _literal); + + explicit IntegerConstantType(bigint _value): m_value(_value) {} + + virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; + + virtual bool operator==(Type const& _other) const override; + + virtual bool canBeStored() const override { return false; } + virtual bool canLiveOutsideStorage() const override { return false; } + virtual unsigned getSizeOnStack() const override { return 1; } + + virtual std::string toString() const override; + virtual u256 literalValue(Literal const* _literal) const override; + + /// @returns the smallest integer type that can hold the value or an empty pointer if not possible. + std::shared_ptr getIntegerType() const; + +private: + bigint m_value; +}; + /** * String type with fixed length, up to 32 bytes. */ @@ -214,7 +240,7 @@ public: virtual bool isValueType() const override { return true; } virtual std::string toString() const override { return "string" + dev::toString(m_bytes); } - virtual u256 literalValue(Literal const& _literal) const override; + virtual u256 literalValue(Literal const* _literal) const override; int getNumBytes() const { return m_bytes; } @@ -231,19 +257,17 @@ public: BoolType() {} virtual Category getCategory() const { return Category::BOOL; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual bool acceptsUnaryOperator(Token::Value _operator) const override + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override { - return _operator == Token::NOT || _operator == Token::DELETE; + return (_operator == Token::NOT || _operator == Token::DELETE) ? shared_from_this() : TypePointer(); } + virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; virtual unsigned getCalldataEncodedSize() const { return 1; } virtual bool isValueType() const override { return true; } virtual std::string toString() const override { return "bool"; } - virtual u256 literalValue(Literal const& _literal) const override; - -protected: - virtual TypePointer binaryOperatorResultImpl(Token::Value _operator, TypePointer const& _this, TypePointer const& _other) const override; + virtual u256 literalValue(Literal const* _literal) const override; }; /** @@ -285,9 +309,9 @@ class StructType: public Type public: virtual Category getCategory() const override { return Category::STRUCT; } StructType(StructDefinition const& _struct): m_struct(_struct) {} - virtual bool acceptsUnaryOperator(Token::Value _operator) const override + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override { - return _operator == Token::DELETE; + return _operator == Token::DELETE ? shared_from_this() : TypePointer(); } virtual bool operator==(Type const& _other) const override; @@ -378,20 +402,12 @@ public: virtual Category getCategory() const override { return Category::VOID; } VoidType() {} + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual std::string toString() const override { return "void"; } virtual bool canBeStored() const override { return false; } virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable void type requested.")); } virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override { return 0; } - -protected: - virtual TypePointer binaryOperatorResultImpl(Token::Value _operator, TypePointer const& _this, TypePointer const& _other) const override - { - (void)_operator; - (void)_this; - (void)_other; - return TypePointer(); - } }; /** @@ -403,24 +419,15 @@ class TypeType: public Type public: virtual Category getCategory() const override { return Category::TYPE; } TypeType(TypePointer const& _actualType): m_actualType(_actualType) {} - TypePointer const& getActualType() const { return m_actualType; } + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } virtual bool canLiveOutsideStorage() const override { return false; } virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; } -protected: - virtual TypePointer binaryOperatorResultImpl(Token::Value _operator, TypePointer const& _this, TypePointer const& _other) const override - { - (void)_operator; - (void)_this; - (void)_other; - return TypePointer(); - } - private: TypePointer m_actualType; }; @@ -437,6 +444,12 @@ public: virtual Category getCategory() const override { return Category::MAGIC; } MagicType(Kind _kind); + + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override + { + return TypePointer(); + } + virtual bool operator==(Type const& _other) const; virtual bool canBeStored() const override { return false; } virtual bool canLiveOutsideStorage() const override { return true; } @@ -445,15 +458,6 @@ public: virtual std::string toString() const override; -protected: - virtual TypePointer binaryOperatorResultImpl(Token::Value _operator, TypePointer const& _this, TypePointer const& _other) const override - { - (void)_operator; - (void)_this; - (void)_other; - return TypePointer(); - } - private: Kind m_kind; diff --git a/test/SolidityCompiler.cpp b/test/SolidityCompiler.cpp index 1a9685f02..b4874e195 100644 --- a/test/SolidityCompiler.cpp +++ b/test/SolidityCompiler.cpp @@ -134,10 +134,10 @@ BOOST_AUTO_TEST_CASE(different_argument_numbers) byte(Instruction::JUMPDEST), // beginning of g byte(Instruction::PUSH1), 0x0, byte(Instruction::PUSH1), 0x0, // initialized e and h - byte(Instruction::PUSH1), byte(0x2a + shift), // ret address - byte(Instruction::PUSH1), 0x1, byte(Instruction::PUSH1), 0xff, byte(Instruction::AND), - byte(Instruction::PUSH1), 0x2, byte(Instruction::PUSH1), 0xff, byte(Instruction::AND), - byte(Instruction::PUSH1), 0x3, byte(Instruction::PUSH1), 0xff, byte(Instruction::AND), + byte(Instruction::PUSH1), byte(0x21 + shift), // ret address + byte(Instruction::PUSH1), 0x1, + byte(Instruction::PUSH1), 0x2, + byte(Instruction::PUSH1), 0x3, byte(Instruction::PUSH1), byte(0x1 + shift), // stack here: ret e h 0x20 1 2 3 0x1 byte(Instruction::JUMP), diff --git a/test/SolidityExpressionCompiler.cpp b/test/SolidityExpressionCompiler.cpp index 9c375418e..579af5bb9 100644 --- a/test/SolidityExpressionCompiler.cpp +++ b/test/SolidityExpressionCompiler.cpp @@ -174,8 +174,8 @@ BOOST_AUTO_TEST_CASE(comparison) bytes code = compileFirstExpression(sourceCode); bytes expectation({byte(eth::Instruction::PUSH1), 0x1, - byte(eth::Instruction::PUSH2), 0x11, 0xaa, byte(eth::Instruction::PUSH2), 0xff, 0xff, byte(eth::Instruction::AND), - byte(eth::Instruction::PUSH2), 0x10, 0xaa, byte(eth::Instruction::PUSH2), 0xff, 0xff, byte(eth::Instruction::AND), + byte(eth::Instruction::PUSH2), 0x11, 0xaa, + byte(eth::Instruction::PUSH2), 0x10, 0xaa, byte(eth::Instruction::LT), byte(eth::Instruction::EQ), byte(eth::Instruction::ISZERO)}); @@ -189,20 +189,18 @@ BOOST_AUTO_TEST_CASE(short_circuiting) "}\n"; bytes code = compileFirstExpression(sourceCode); - bytes expectation({byte(eth::Instruction::PUSH1), 0xa, - byte(eth::Instruction::PUSH1), 0x8, - byte(eth::Instruction::ADD), byte(eth::Instruction::PUSH1), 0xff, byte(eth::Instruction::AND), - byte(eth::Instruction::PUSH1), 0x4, byte(eth::Instruction::PUSH1), 0xff, byte(eth::Instruction::AND), + bytes expectation({byte(eth::Instruction::PUSH1), 0x12, // 8 + 10 + byte(eth::Instruction::PUSH1), 0x4, byte(eth::Instruction::GT), - byte(eth::Instruction::ISZERO), // after this we have 10 + 8 >= 4 + byte(eth::Instruction::ISZERO), // after this we have 4 <= 8 + 10 byte(eth::Instruction::DUP1), - byte(eth::Instruction::PUSH1), 0x20, + byte(eth::Instruction::PUSH1), 0x11, byte(eth::Instruction::JUMPI), // short-circuit if it is true byte(eth::Instruction::POP), - byte(eth::Instruction::PUSH1), 0x2, byte(eth::Instruction::PUSH1), 0xff, byte(eth::Instruction::AND), - byte(eth::Instruction::PUSH1), 0x9, byte(eth::Instruction::PUSH1), 0xff, byte(eth::Instruction::AND), + byte(eth::Instruction::PUSH1), 0x2, + byte(eth::Instruction::PUSH1), 0x9, byte(eth::Instruction::EQ), - byte(eth::Instruction::ISZERO), // after this we have 2 != 9 + byte(eth::Instruction::ISZERO), // after this we have 9 != 2 byte(eth::Instruction::JUMPDEST), byte(eth::Instruction::PUSH1), 0x1, byte(eth::Instruction::EQ), @@ -213,28 +211,24 @@ BOOST_AUTO_TEST_CASE(short_circuiting) BOOST_AUTO_TEST_CASE(arithmetics) { char const* sourceCode = "contract test {\n" - " function f() { var x = ((((((((9 ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); }" + " function f(uint y) { var x = ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); }" "}\n"; - bytes code = compileFirstExpression(sourceCode); + bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}, {"test", "f", "x"}}); bytes expectation({byte(eth::Instruction::PUSH1), 0x1, byte(eth::Instruction::PUSH1), 0x2, - byte(eth::Instruction::PUSH1), 0xff, byte(eth::Instruction::AND), byte(eth::Instruction::PUSH1), 0x3, - byte(eth::Instruction::PUSH1), 0xff, byte(eth::Instruction::AND), byte(eth::Instruction::PUSH1), 0x4, byte(eth::Instruction::PUSH1), 0x5, byte(eth::Instruction::PUSH1), 0x6, byte(eth::Instruction::PUSH1), 0x7, byte(eth::Instruction::PUSH1), 0x8, - byte(eth::Instruction::PUSH1), 0x9, + byte(eth::Instruction::DUP10), byte(eth::Instruction::XOR), byte(eth::Instruction::AND), byte(eth::Instruction::OR), byte(eth::Instruction::SUB), byte(eth::Instruction::ADD), - byte(eth::Instruction::PUSH1), 0xff, byte(eth::Instruction::AND), byte(eth::Instruction::MOD), - byte(eth::Instruction::PUSH1), 0xff, byte(eth::Instruction::AND), byte(eth::Instruction::DIV), byte(eth::Instruction::MUL)}); BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); @@ -243,15 +237,15 @@ BOOST_AUTO_TEST_CASE(arithmetics) BOOST_AUTO_TEST_CASE(unary_operators) { char const* sourceCode = "contract test {\n" - " function f() { var x = !(~+- 1 == 2); }" + " function f(int y) { var x = !(~+- y == 2); }" "}\n"; - bytes code = compileFirstExpression(sourceCode); + bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}, {"test", "f", "x"}}); - bytes expectation({byte(eth::Instruction::PUSH1), 0x2, byte(eth::Instruction::PUSH1), 0xff, byte(eth::Instruction::AND), - byte(eth::Instruction::PUSH1), 0x1, + bytes expectation({byte(eth::Instruction::PUSH1), 0x2, + byte(eth::Instruction::DUP3), byte(eth::Instruction::PUSH1), 0x0, byte(eth::Instruction::SUB), - byte(eth::Instruction::NOT), byte(eth::Instruction::PUSH1), 0xff, byte(eth::Instruction::AND), + byte(eth::Instruction::NOT), byte(eth::Instruction::EQ), byte(eth::Instruction::ISZERO)}); BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); @@ -315,7 +309,7 @@ BOOST_AUTO_TEST_CASE(assignment) bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "b"}}); // Stack: a, b - bytes expectation({byte(eth::Instruction::PUSH1), 0x2, byte(eth::Instruction::PUSH1), 0xff, byte(eth::Instruction::AND), + bytes expectation({byte(eth::Instruction::PUSH1), 0x2, byte(eth::Instruction::DUP2), byte(eth::Instruction::DUP4), byte(eth::Instruction::ADD), @@ -338,14 +332,14 @@ BOOST_AUTO_TEST_CASE(function_call) {{"test", "f", "a"}, {"test", "f", "b"}}); // Stack: a, b - bytes expectation({byte(eth::Instruction::PUSH1), 0x02, byte(eth::Instruction::PUSH1), 0xff, byte(eth::Instruction::AND), - byte(eth::Instruction::PUSH1), 0x12, - byte(eth::Instruction::PUSH1), 0x01, byte(eth::Instruction::PUSH1), 0xff, byte(eth::Instruction::AND), + bytes expectation({byte(eth::Instruction::PUSH1), 0x02, + byte(eth::Instruction::PUSH1), 0x0c, + byte(eth::Instruction::PUSH1), 0x01, byte(eth::Instruction::DUP5), byte(eth::Instruction::ADD), // Stack here: a b 2 (a+1) byte(eth::Instruction::DUP4), - byte(eth::Instruction::PUSH1), 0x19, + byte(eth::Instruction::PUSH1), 0x13, byte(eth::Instruction::JUMP), byte(eth::Instruction::JUMPDEST), // Stack here: a b 2 g(a+1, b) @@ -363,40 +357,36 @@ BOOST_AUTO_TEST_CASE(function_call) BOOST_AUTO_TEST_CASE(negative_literals_8bits) { - // these all fit in 8 bits char const* sourceCode = "contract test {\n" - " function f() { int8 x = -0 + -1 + -0x01 + -127 + -128; }\n" + " function f() { int8 x = -0x80; }\n" "}\n"; bytes code = compileFirstExpression(sourceCode); - bytes expectation(bytes({byte(eth::Instruction::PUSH32)}) + bytes(31, 0xff) + bytes(1, 0x80) + - bytes({byte(eth::Instruction::PUSH32)}) + bytes(31, 0xff) + bytes(1, 0x81) + - bytes({byte(eth::Instruction::PUSH32)}) + bytes(32, 0xff) + - bytes({byte(eth::Instruction::PUSH32)}) + bytes(32, 0xff) + - bytes({byte(eth::Instruction::PUSH1), 0x00, - byte(eth::Instruction::ADD), - byte(eth::Instruction::ADD), - byte(eth::Instruction::ADD), - byte(eth::Instruction::ADD)})); + bytes expectation(bytes({byte(eth::Instruction::PUSH32)}) + bytes(31, 0xff) + bytes(1, 0x80)); BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } BOOST_AUTO_TEST_CASE(negative_literals_16bits) { - // -1 should need 8 bits, -129 should need 16 bits, how many bits are used is visible - // from the SIGNEXTEND opcodes char const* sourceCode = "contract test {\n" - " function f() { int64 x = int64(-1 + -129); }\n" + " function f() { int64 x = ~0xabc; }\n" + "}\n"; + bytes code = compileFirstExpression(sourceCode); + + bytes expectation(bytes({byte(eth::Instruction::PUSH32)}) + bytes(30, 0xff) + bytes{0xf5, 0x43}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + +BOOST_AUTO_TEST_CASE(intermediately_overflowing_literals) +{ + // first literal itself is too large for 256 bits but it fits after all constant operations + // have been applied + char const* sourceCode = "contract test {\n" + " function f() { var x = (0xffffffffffffffffffffffffffffffffffffffff * 0xffffffffffffffffffffffffff01) & 0xbf; }\n" "}\n"; bytes code = compileFirstExpression(sourceCode); - bytes expectation(bytes({byte(eth::Instruction::PUSH32)}) + bytes(31, 0xff) + bytes(1, 0x7f) + - bytes({byte(eth::Instruction::PUSH32)}) + bytes(32, 0xff) + - bytes({byte(eth::Instruction::PUSH1), 0x00, - byte(eth::Instruction::SIGNEXTEND), - byte(eth::Instruction::ADD), - byte(eth::Instruction::PUSH1), 0x01, - byte(eth::Instruction::SIGNEXTEND)})); + bytes expectation(bytes({byte(eth::Instruction::PUSH1), 0xbf})); BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } diff --git a/test/SolidityOptimizer.cpp b/test/SolidityOptimizer.cpp index 972332502..41ec1f90c 100644 --- a/test/SolidityOptimizer.cpp +++ b/test/SolidityOptimizer.cpp @@ -94,7 +94,7 @@ BOOST_AUTO_TEST_CASE(large_integers) b = 0x10000000000000000000000002; } })"; - compileBothVersions(58, sourceCode); + compileBothVersions(36, sourceCode); compareVersions("f()"); } @@ -106,7 +106,7 @@ BOOST_AUTO_TEST_CASE(invariants) return int(0) | (int(1) * (int(0) ^ (0 + a))); } })"; - compileBothVersions(53, sourceCode); + compileBothVersions(41, sourceCode); compareVersions("f(uint256)", u256(0x12334664)); } @@ -120,7 +120,7 @@ BOOST_AUTO_TEST_CASE(unused_expressions) data; } })"; - compileBothVersions(36, sourceCode); + compileBothVersions(33, sourceCode); compareVersions("f()"); } @@ -135,7 +135,7 @@ BOOST_AUTO_TEST_CASE(constant_folding_both_sides) return 98 ^ (7 * ((1 | (x | 1000)) * 40) ^ 102); } })"; - compileBothVersions(56, sourceCode); + compileBothVersions(37, sourceCode); compareVersions("f(uint256)"); } diff --git a/test/SolidityScanner.cpp b/test/SolidityScanner.cpp index b7942d293..7dc9ef482 100644 --- a/test/SolidityScanner.cpp +++ b/test/SolidityScanner.cpp @@ -103,14 +103,17 @@ BOOST_AUTO_TEST_CASE(negative_numbers) BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::VAR); BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); BOOST_CHECK_EQUAL(scanner.next(), Token::ASSIGN); + BOOST_CHECK_EQUAL(scanner.next(), Token::SUB); BOOST_CHECK_EQUAL(scanner.next(), Token::NUMBER); - BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "-.2"); + BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), ".2"); BOOST_CHECK_EQUAL(scanner.next(), Token::ADD); + BOOST_CHECK_EQUAL(scanner.next(), Token::SUB); BOOST_CHECK_EQUAL(scanner.next(), Token::NUMBER); - BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "-0x78"); + BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "0x78"); BOOST_CHECK_EQUAL(scanner.next(), Token::ADD); + BOOST_CHECK_EQUAL(scanner.next(), Token::SUB); BOOST_CHECK_EQUAL(scanner.next(), Token::NUMBER); - BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "-7.3"); + BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "7.3"); BOOST_CHECK_EQUAL(scanner.next(), Token::ADD); BOOST_CHECK_EQUAL(scanner.next(), Token::NUMBER); BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "8.9"); @@ -130,8 +133,9 @@ BOOST_AUTO_TEST_CASE(locations) BOOST_CHECK_EQUAL(scanner.next(), Token::SEMICOLON); BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 24); BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 25); + BOOST_CHECK_EQUAL(scanner.next(), Token::SUB); BOOST_CHECK_EQUAL(scanner.next(), Token::NUMBER); - BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 26); + BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 27); BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 32); BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 45);