From b8b4f5e9f9a89eac1218551b5da322b41c7813f4 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sat, 30 Apr 2016 00:15:22 +0100 Subject: Support bitshifting in variables --- Changelog.md | 1 + libsolidity/ast/Types.cpp | 5 + libsolidity/codegen/ExpressionCompiler.cpp | 49 ++++- libsolidity/codegen/ExpressionCompiler.h | 2 +- test/libsolidity/SolidityEndToEndTest.cpp | 298 +++++++++++++++++++++++++++++ 5 files changed, 346 insertions(+), 9 deletions(-) diff --git a/Changelog.md b/Changelog.md index 1eb90c22..9bc465ee 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.4.7 (unreleased) Features: + * Bitshift operators. * Type checker: Warn when ``msg.value`` is used in non-payable function. * Code generator: Inject the Swarm hash of a metadata file into the bytecode. * Optimizer: Some dead code elimination. diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index d9660bc0..6b4dd432 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -347,6 +347,11 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe // All integer types can be compared if (Token::isCompareOp(_operator)) return commonType; + // Disable >>> here. + if (_operator == Token::SHR) + return TypePointer(); + if (Token::isShiftOp(_operator) && !isAddress()) // && !_other->isAddress()) + return shared_from_this(); if (Token::isBooleanOp(_operator)) return TypePointer(); if (auto intType = dynamic_pointer_cast(commonType)) diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 5748d818..d28ffed8 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -211,6 +211,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) Token::Value op = _assignment.assignmentOperator(); if (op != Token::Assign) // compound assignment { + Token::Value target_op = Token::AssignmentToBinaryOp(op); solUnimplementedAssert(_assignment.annotation().type->isValueType(), "Compound operators not implemented for non-value types."); unsigned lvalueSize = m_currentLValue->sizeOnStack(); unsigned itemSize = _assignment.annotation().type->sizeOnStack(); @@ -221,7 +222,11 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) // value lvalue_ref value lvalue_ref } m_currentLValue->retrieveValue(_assignment.location(), true); - appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.annotation().type); + if (Token::isShiftOp(target_op)) + // shift only cares about the signedness of both sides + appendShiftOperatorCode(target_op, *_assignment.leftHandSide().annotation().type, *_assignment.rightHandSide().annotation().type); + else + appendOrdinaryBinaryOperatorCode(target_op, *_assignment.annotation().type); if (lvalueSize > 0) { solAssert(itemSize + lvalueSize <= 16, "Stack too deep, try removing local variables."); @@ -361,7 +366,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) else { bool cleanupNeeded = false; - if (Token::isCompareOp(c_op)) + if (Token::isCompareOp(c_op) || Token::isShiftOp(c_op)) cleanupNeeded = true; if (commonType.category() == Type::Category::Integer && (c_op == Token::Div || c_op == Token::Mod)) cleanupNeeded = true; @@ -386,7 +391,10 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) leftExpression.accept(*this); utils().convertType(*leftExpression.annotation().type, commonType, cleanupNeeded); } - if (Token::isCompareOp(c_op)) + if (Token::isShiftOp(c_op)) + // shift only cares about the signedness of both sides + appendShiftOperatorCode(c_op, *leftExpression.annotation().type, *rightExpression.annotation().type); + else if (Token::isCompareOp(c_op)) appendCompareOperatorCode(c_op, commonType); else appendOrdinaryBinaryOperatorCode(c_op, commonType); @@ -1326,8 +1334,6 @@ void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator appendArithmeticOperatorCode(_operator, _type); else if (Token::isBitOp(_operator)) appendBitOperatorCode(_operator); - else if (Token::isShiftOp(_operator)) - appendShiftOperatorCode(_operator); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown binary operator.")); } @@ -1390,17 +1396,44 @@ void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) } } -void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator) +void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type const& _leftType, Type const& _rightType) { - solUnimplemented("Shift operators not yet implemented."); + // stack: rvalue lvalue + + bool c_leftSigned = false; + if (auto leftType = dynamic_cast(&_leftType)) + c_leftSigned = leftType->isSigned(); + else + solUnimplemented("Only IntegerType can be shifted."); + + // The RValue can be a RationalNumberType too. + bool c_rightSigned = false; + if (auto rightType = dynamic_cast(&_rightType)) + { + solAssert(rightType->integerType(), "integerType() called for fractional number."); + c_rightSigned = rightType->integerType()->isSigned(); + } + else if (auto rightType = dynamic_cast(&_rightType)) + c_rightSigned = rightType->isSigned(); + else + solUnimplemented("Not implemented yet - FixedPointType."); + + // shift with negative rvalue throws exception + if (c_rightSigned) + { + m_context << u256(0) << Instruction::DUP3 << Instruction::SLT; + m_context.appendConditionalJumpTo(m_context.errorTag()); + } + switch (_operator) { case Token::SHL: + m_context << Instruction::SWAP1 << u256(2) << Instruction::EXP << Instruction::MUL; break; case Token::SAR: + m_context << Instruction::SWAP1 << u256(2) << Instruction::EXP << Instruction::SWAP1 << (c_leftSigned ? Instruction::SDIV : Instruction::DIV); break; case Token::SHR: - break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown shift operator.")); } diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index f08bded9..e6cf382c 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -91,7 +91,7 @@ private: void appendArithmeticOperatorCode(Token::Value _operator, Type const& _type); void appendBitOperatorCode(Token::Value _operator); - void appendShiftOperatorCode(Token::Value _operator); + void appendShiftOperatorCode(Token::Value _operator, Type const& _leftType, Type const& _rightType); /// @} /// Appends code to call a function of the given type with the given arguments. diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 2df6e9f2..837caa2d 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -8456,6 +8456,304 @@ BOOST_AUTO_TEST_CASE(shift_negative_constant_right) BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(-0x42))); } +BOOST_AUTO_TEST_CASE(shift_left) +{ + char const* sourceCode = R"( + contract C { + function f(uint a, uint b) returns (uint) { + return a << b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(0)) == encodeArgs(u256(0x4266))); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(8)) == encodeArgs(u256(0x426600))); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(16)) == encodeArgs(u256(0x42660000))); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(17)) == encodeArgs(u256(0x84cc0000))); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(240)) == fromHex("4266000000000000000000000000000000000000000000000000000000000000")); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(256)) == encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(shift_left_uint32) +{ + char const* sourceCode = R"( + contract C { + function f(uint32 a, uint32 b) returns (uint) { + return a << b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(0)) == encodeArgs(u256(0x4266))); + BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(8)) == encodeArgs(u256(0x426600))); + BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(16)) == encodeArgs(u256(0x42660000))); + BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(17)) == encodeArgs(u256(0x84cc0000))); + BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(32)) == encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(shift_left_uint8) +{ + char const* sourceCode = R"( + contract C { + function f(uint8 a, uint8 b) returns (uint) { + return a << b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint8,uint8)", u256(0x66), u256(0)) == encodeArgs(u256(0x66))); + BOOST_CHECK(callContractFunction("f(uint8,uint8)", u256(0x66), u256(8)) == encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(shift_left_assignment) +{ + char const* sourceCode = R"( + contract C { + function f(uint a, uint b) returns (uint) { + a <<= b; + return a; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(0)) == encodeArgs(u256(0x4266))); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(8)) == encodeArgs(u256(0x426600))); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(16)) == encodeArgs(u256(0x42660000))); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(17)) == encodeArgs(u256(0x84cc0000))); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(240)) == fromHex("4266000000000000000000000000000000000000000000000000000000000000")); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(256)) == encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(shift_right) +{ + char const* sourceCode = R"( + contract C { + function f(uint a, uint b) returns (uint) { + return a >> b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(0)) == encodeArgs(u256(0x4266))); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(8)) == encodeArgs(u256(0x42))); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(16)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(17)) == encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(shift_right_garbled) +{ + char const* sourceCode = R"( + contract C { + function f(uint8 a, uint8 b) returns (uint) { + assembly { + a := 0xffffffff + } + // Higher bits should be cleared before the shift + return a >> b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x0), u256(4)) == encodeArgs(u256(0xf))); +} + +BOOST_AUTO_TEST_CASE(shift_right_uint32) +{ + char const* sourceCode = R"( + contract C { + function f(uint32 a, uint32 b) returns (uint) { + return a >> b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(0)) == encodeArgs(u256(0x4266))); + BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(8)) == encodeArgs(u256(0x42))); + BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(16)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("f(uint32,uint32)", u256(0x4266), u256(17)) == encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(shift_right_uint8) +{ + char const* sourceCode = R"( + contract C { + function f(uint8 a, uint8 b) returns (uint) { + return a >> b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint8,uint8)", u256(0x66), u256(0)) == encodeArgs(u256(0x66))); + BOOST_CHECK(callContractFunction("f(uint8,uint8)", u256(0x66), u256(8)) == encodeArgs(u256(0x0))); +} + +BOOST_AUTO_TEST_CASE(shift_right_assignment) +{ + char const* sourceCode = R"( + contract C { + function f(uint a, uint b) returns (uint) { + a >>= b; + return a; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(0)) == encodeArgs(u256(0x4266))); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(8)) == encodeArgs(u256(0x42))); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(16)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", u256(0x4266), u256(17)) == encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue) +{ + char const* sourceCode = R"( + contract C { + function f(int a, int b) returns (int) { + return a >> b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(0)) == encodeArgs(u256(-4266))); + BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(8)) == encodeArgs(u256(-16))); + BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(16)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(17)) == encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_assignment) +{ + char const* sourceCode = R"( + contract C { + function f(int a, int b) returns (int) { + a >>= b; + return a; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(0)) == encodeArgs(u256(-4266))); + BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(8)) == encodeArgs(u256(-16))); + BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(16)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("f(int256,int256)", u256(-4266), u256(17)) == encodeArgs(u256(0))); +} + +BOOST_AUTO_TEST_CASE(shift_negative_rvalue) +{ + char const* sourceCode = R"( + contract C { + function f(int a, int b) returns (int) { + return a << b; + } + function g(int a, int b) returns (int) { + return a >> b; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(int256,int256)", u256(1), u256(-1)) == encodeArgs()); + BOOST_CHECK(callContractFunction("g(int256,int256)", u256(1), u256(-1)) == encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(shift_negative_rvalue_assignment) +{ + char const* sourceCode = R"( + contract C { + function f(int a, int b) returns (int) { + a <<= b; + return a; + } + function g(int a, int b) returns (int) { + a >>= b; + return a; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(int256,int256)", u256(1), u256(-1)) == encodeArgs()); + BOOST_CHECK(callContractFunction("g(int256,int256)", u256(1), u256(-1)) == encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(shift_constant_left_assignment) +{ + char const* sourceCode = R"( + contract C { + function f() returns (uint a) { + a = 0x42; + a <<= 8; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0x4200))); +} + +BOOST_AUTO_TEST_CASE(shift_constant_right_assignment) +{ + char const* sourceCode = R"( + contract C { + function f() returns (uint a) { + a = 0x4200; + a >>= 8; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0x42))); +} + +BOOST_AUTO_TEST_CASE(shift_cleanup) +{ + char const* sourceCode = R"( + contract C { + function f() returns (uint16 x) { + x = 0xffff; + x += 32; + x <<= 8; + x >>= 16; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0x0))); +} + +BOOST_AUTO_TEST_CASE(shift_cleanup_garbled) +{ + char const* sourceCode = R"( + contract C { + function f() returns (uint8 x) { + assembly { + x := 0xffff + } + x >>= 8; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0x0))); +} + +BOOST_AUTO_TEST_CASE(shift_overflow) +{ + char const* sourceCode = R"( + contract C { + function leftU(uint8 x, uint8 y) returns (uint8) { + return x << y; + } + function leftS(int8 x, int8 y) returns (int8) { + return x << y; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("leftU(uint8,uint8)", 255, 8) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("leftU(uint8,uint8)", 255, 1) == encodeArgs(u256(254))); + BOOST_CHECK(callContractFunction("leftU(uint8,uint8)", 255, 0) == encodeArgs(u256(255))); + + BOOST_CHECK(callContractFunction("leftS(int8,int8)", 1, 7) == encodeArgs(u256(128))); + BOOST_CHECK(callContractFunction("leftS(int8,int8)", 1, 6) == encodeArgs(u256(64))); +} + BOOST_AUTO_TEST_CASE(inline_assembly_in_modifiers) { char const* sourceCode = R"( -- cgit