diff options
-rw-r--r-- | Changelog.md | 5 | ||||
-rw-r--r-- | docs/control-structures.rst | 64 | ||||
-rw-r--r-- | docs/types.rst | 21 | ||||
-rw-r--r-- | libdevcore/UTF8.h | 2 | ||||
-rw-r--r-- | libsolidity/ast/Types.cpp | 9 | ||||
-rw-r--r-- | libsolidity/ast/Types.h | 2 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.cpp | 21 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerUtils.h | 3 | ||||
-rw-r--r-- | libsolidity/codegen/LValue.cpp | 19 | ||||
-rw-r--r-- | test/libsolidity/SolidityEndToEndTest.cpp | 96 | ||||
-rw-r--r-- | test/libsolidity/SolidityNameAndTypeResolution.cpp | 20 |
11 files changed, 244 insertions, 18 deletions
diff --git a/Changelog.md b/Changelog.md index 468518d2..0b10cd0c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,8 @@ +### 0.4.7 (unreleased) + +Bugfixes: + * Type checker: string literals that are not valid UTF-8 cannot be converted to string type + ### 0.4.6 (2016-11-22) Bugfixes: diff --git a/docs/control-structures.rst b/docs/control-structures.rst index f57e7936..974a093f 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -2,12 +2,62 @@ Expressions and Control Structures ################################## +.. index:: ! parameter, parameter;input, parameter;output + +Input Parameters and Output Parameters +====================================== + +As in Javascript, functions may take parameters as input; +unlike in Javascript and C, they may also return arbitrary number of +parameters as output. + +Input Parameters +---------------- + +The input parameters are declared the same way as variables are. As an +exception, unused parameters can omit the variable name. +For example, suppose we want our contract to +accept one kind of external calls with two integers, we would write +something like:: + + contract Simple { + function taker(uint _a, uint _b) { + // do something with _a and _b. + } + } + +Output Parameters +----------------- + +The output parameters can be declared with the same syntax after the +``returns`` keyword. For example, suppose we wished to return two results: +the sum and the product of the two given integers, then we would +write:: + + contract Simple { + function arithmetics(uint _a, uint _b) returns (uint o_sum, uint o_product) { + o_sum = _a + _b; + o_product = _a * _b; + } + } + +The names of output parameters can be omitted. +The output values can also be specified using ``return`` statements. +The ``return`` statements are also capable of returning multiple +values, see :ref:`multi-return`. +Return parameters are initialized to zero; if they are not explicitly +set, they stay to be zero. + +Input parameters and output parameters can be used as expressions in +the function body. There, they are also usable in the left-hand side +of assignment. + .. index:: if, else, while, do/while, for, break, continue, return, switch, goto Control Structures =================== -Most of the control structures from C or JavaScript are available in Solidity +Most of the control structures from JavaScript are available in Solidity except for ``switch`` and ``goto``. So there is: ``if``, ``else``, ``while``, ``do``, ``for``, ``break``, ``continue``, ``return``, ``? :``, with the usual semantics known from C or JavaScript. @@ -16,7 +66,17 @@ Parentheses can *not* be omitted for conditionals, but curly brances can be omit around single-statement bodies. Note that there is no type conversion from non-boolean to boolean types as -there is in C and JavaScript, so ``if (1) { ... }`` is *not* valid Solidity. +there is in C and JavaScript, so ``if (1) { ... }`` is *not* valid +Solidity. + +.. _multi-return: + +Returning Multiple Values +------------------------- + +When a function has multiple output parameters, ``return (v0, v1, ..., +vn)`` can return multiple values. The number of components must be +the same as the number of output parameters. .. index:: ! function;call, function;internal, function;external diff --git a/docs/types.rst b/docs/types.rst index 21575cfb..0436fc70 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -169,9 +169,10 @@ Fixed Point Numbers Rational and Integer Literals ----------------------------- -All number literals retain arbitrary precision until they are converted to a non-literal type (i.e. by -using them together with a non-literal type). This means that computations do not overflow but also -divisions do not truncate. +Number literal expressions retain arbitrary precision until they are converted to a non-literal type (i.e. by +using them together with a non-literal expression). +This means that computations do not overflow and divisions do not truncate +in number literal expressions. For example, ``(2**800 + 1) - 2**800`` results in the constant ``1`` (of type ``uint8``) although intermediate results would not even fit the machine word size. Furthermore, ``.5 * 8`` results @@ -185,12 +186,20 @@ In ``var x = 1/4;``, ``x`` will receive the type ``ufixed0x8`` while in ``var x the type ``ufixed0x256`` because ``1/3`` is not finitely representable in binary and will thus be approximated. -Any operator that can be applied to integers can also be applied to literal expressions as +Any operator that can be applied to integers can also be applied to number literal expressions as long as the operands are integers. If any of the two is fractional, bit operations are disallowed and exponentiation is disallowed if the exponent is fractional (because that might result in a non-rational number). .. note:: + Solidity has a number literal type for each rational number. + Integer literals and rational number literals belong to number literal types. + Moreover, all number literal expressions (i.e. the expressions that + contain only number literals and operators) belong to number literal + types. So the number literal expressions `1 + 2` and `2 + 1` both + belong to the same number literal type for the rational number three. + +.. note:: Most finite decimal fractions like ``5.3743`` are not finitely representable in binary. The correct type for ``5.3743`` is ``ufixed8x248`` because that allows to best approximate the number. If you want to use the number together with types like ``ufixed`` (i.e. ``ufixed128x128``), you have to explicitly @@ -200,7 +209,7 @@ a non-rational number). Division on integer literals used to truncate in earlier versions, but it will now convert into a rational number, i.e. ``5 / 2`` is not equal to ``2``, but to ``2.5``. .. note:: - Literal expressions are converted to a permanent type as soon as they are used with other + Number literal expressions are converted into a non-literal type as soon as they are used with non-literal expressions. Even though we know that the value of the expression assigned to ``b`` in the following example evaluates to an integer, it still uses fixed point types (and not rational number literals) in between and so the code @@ -216,7 +225,7 @@ a non-rational number). String Literals --------------- -String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``. +String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). They do not imply trailing zeroes as in C; `"foo"`` represents three bytes not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``. String literals support escape characters, such as ``\n``, ``\xNN`` and ``\uNNNN``. ``\xNN`` takes a hex value and inserts the appropriate byte, while ``\uNNNN`` takes a Unicode codepoint and inserts an UTF-8 sequence. diff --git a/libdevcore/UTF8.h b/libdevcore/UTF8.h index 3e39273c..9bdc2b4f 100644 --- a/libdevcore/UTF8.h +++ b/libdevcore/UTF8.h @@ -29,7 +29,7 @@ namespace dev { /// Validate an input for UTF8 encoding -/// @returns true if it is invalid and the first invalid position in invalidPosition +/// @returns false if it is invalid and the first invalid position in invalidPosition bool validate(std::string const& _input, size_t& _invalidPosition); } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index b7de3646..b22f3c08 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -879,7 +879,8 @@ bool StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const else if (auto arrayType = dynamic_cast<ArrayType const*>(&_convertTo)) return arrayType->isByteArray() && - !(arrayType->dataStoredIn(DataLocation::Storage) && arrayType->isPointer()); + !(arrayType->dataStoredIn(DataLocation::Storage) && arrayType->isPointer()) && + !(arrayType->isString() && !isValidUTF8()); else return false; } @@ -906,6 +907,12 @@ TypePointer StringLiteralType::mobileType() const return make_shared<ArrayType>(DataLocation::Memory, true); } +bool StringLiteralType::isValidUTF8() const +{ + size_t dontCare {}; + return dev::validate(m_value, dontCare); +} + shared_ptr<FixedBytesType> FixedBytesType::smallestTypeForLiteral(string const& _literal) { if (_literal.length() <= 32) diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index b713a7c0..72640a1c 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -425,6 +425,8 @@ public: virtual std::string toString(bool) const override; virtual TypePointer mobileType() const override; + bool isValidUTF8() const; + std::string const& value() const { return m_value; } private: diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index fe2b9c7e..d5361ac6 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -358,7 +358,7 @@ void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function) Instruction::OR; } -void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) +void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded, bool _chopSignBits) { // For a type extension, we need to remove all higher-order bits that we might have ignored in // previous operations. @@ -370,6 +370,12 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp Type::Category targetTypeCategory = _targetType.category(); bool enumOverflowCheckPending = (targetTypeCategory == Type::Category::Enum || stackTypeCategory == Type::Category::Enum); + bool chopSignBitsPending = _chopSignBits && targetTypeCategory == Type::Category::Integer; + if (chopSignBitsPending) + { + const IntegerType& targetIntegerType = dynamic_cast<const IntegerType &>(_targetType); + chopSignBitsPending = targetIntegerType.isSigned(); + } switch (stackTypeCategory) { @@ -482,6 +488,14 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp cleanHigherOrderBits(typeOnStack); else if (_cleanupNeeded) cleanHigherOrderBits(targetType); + if (chopSignBitsPending) + { + if (typeOnStack.numBits() < 256) + m_context + << ((u256(1) << typeOnStack.numBits()) - 1) + << Instruction::AND; + chopSignBitsPending = false; + } } } break; @@ -724,10 +738,15 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp default: // All other types should not be convertible to non-equal types. solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); + if (_cleanupNeeded && _targetType.canBeStored() && _targetType.storageBytes() < 32) + m_context + << ((u256(1) << (8 * _targetType.storageBytes())) - 1) + << Instruction::AND; break; } solAssert(!enumOverflowCheckPending, "enum overflow checking missing."); + solAssert(!chopSignBitsPending, "forgot to chop the sign bits."); } void CompilerUtils::pushZeroValue(Type const& _type) diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index ff87124f..4baf48ff 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -130,7 +130,8 @@ public: /// if a reference type is converted from calldata or storage to memory. /// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be /// necessary. - void convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false); + /// If @a _chopSignBits, the function resets the signed bits out of the width of the signed integer. + void convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false, bool _chopSignBits = false); /// Creates a zero-value for the given type and puts it onto the stack. This might allocate /// memory for memory references. diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index 3f1730d1..23fe2d4e 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -216,11 +216,14 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const { CompilerUtils utils(m_context); + solAssert(m_dataType, ""); + // stack: value storage_key storage_offset if (m_dataType->isValueType()) { solAssert(m_dataType->storageBytes() <= 32, "Invalid storage bytes size."); solAssert(m_dataType->storageBytes() > 0, "Invalid storage bytes size."); + if (m_dataType->storageBytes() == 32) { solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size."); @@ -228,6 +231,11 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc m_context << Instruction::POP; if (!_move) m_context << Instruction::DUP2 << Instruction::SWAP1; + + m_context << Instruction::SWAP1; + utils.convertType(_sourceType, *m_dataType, true); + m_context << Instruction::SWAP1; + m_context << Instruction::SSTORE; } else @@ -248,6 +256,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc // stack: value storage_ref cleared_value multiplier value if (FunctionType const* fun = dynamic_cast<decltype(fun)>(m_dataType)) { + solAssert(_sourceType == *m_dataType, "function item stored but target is not equal to source"); if (fun->location() == FunctionType::Location::External) // Combine the two-item function type into a single stack slot. utils.combineExternalFunctionType(false); @@ -257,19 +266,17 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc Instruction::AND; } else if (m_dataType->category() == Type::Category::FixedBytes) + { + solAssert(_sourceType.category() == Type::Category::FixedBytes, "source not fixed bytes"); m_context << (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes())) << Instruction::SWAP1 << Instruction::DIV; + } else { solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size for opaque type."); // remove the higher order bits - m_context - << (u256(1) << (8 * (32 - m_dataType->storageBytes()))) - << Instruction::SWAP1 - << Instruction::DUP2 - << Instruction::MUL - << Instruction::DIV; + utils.convertType(_sourceType, *m_dataType, true, true); } m_context << Instruction::MUL << Instruction::OR; // stack: value storage_ref updated_value diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index a9a88789..6478ea86 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -4506,6 +4506,102 @@ BOOST_AUTO_TEST_CASE(external_types_in_calls) BOOST_CHECK(callContractFunction("t2()") == encodeArgs(u256(9))); } +BOOST_AUTO_TEST_CASE(invalid_enum_compared) +{ + char const* sourceCode = R"( + contract C { + enum X { A, B } + + function test_eq() returns (bool) { + X garbled; + assembly { + garbled := 5 + } + return garbled == garbled; + } + function test_eq_ok() returns (bool) { + X garbled = X.A; + return garbled == garbled; + } + function test_neq() returns (bool) { + X garbled; + assembly { + garbled := 5 + } + return garbled != garbled; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test_eq_ok()") == encodeArgs(u256(1))); + // both should throw + BOOST_CHECK(callContractFunction("test_eq()") == encodeArgs()); + BOOST_CHECK(callContractFunction("test_neq()") == encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(invalid_enum_logged) +{ + char const* sourceCode = R"( + contract C { + enum X { A, B } + event Log(X); + + function test_log() returns (uint) { + X garbled = X.A; + assembly { + garbled := 5 + } + Log(garbled); + return 1; + } + function test_log_ok() returns (uint) { + X x = X.A; + Log(x); + return 1; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test_log_ok()") == encodeArgs(u256(1))); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_REQUIRE_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Log(uint8)"))); + BOOST_CHECK_EQUAL(h256(m_logs[0].data), h256(u256(0))); + + // should throw + BOOST_CHECK(callContractFunction("test_log()") == encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(invalid_enum_stored) +{ + char const* sourceCode = R"( + contract C { + enum X { A, B } + X public x; + + function test_store() returns (uint) { + X garbled = X.A; + assembly { + garbled := 5 + } + x = garbled; + return 1; + } + function test_store_ok() returns (uint) { + x = X.A; + return 1; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test_store_ok()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(0))); + + // should throw + BOOST_CHECK(callContractFunction("test_store()") == encodeArgs()); +} + BOOST_AUTO_TEST_CASE(invalid_enum_as_external_ret) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 6dc7ac8c..7a132068 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -2038,6 +2038,26 @@ BOOST_AUTO_TEST_CASE(string) BOOST_CHECK_NO_THROW(parseAndAnalyse(sourceCode)); } +BOOST_AUTO_TEST_CASE(invalid_utf8_implicit) +{ + char const* sourceCode = R"( + contract C { + string s = "\xa0\x00"; + } + )"; + CHECK_ERROR(sourceCode, TypeError, "invalid UTF-8"); +} + +BOOST_AUTO_TEST_CASE(invalid_utf8_explicit) +{ + char const* sourceCode = R"( + contract C { + string s = string("\xa0\x00"); + } + )"; + CHECK_ERROR(sourceCode, TypeError, "Explicit type conversion not allowed"); +} + BOOST_AUTO_TEST_CASE(string_index) { char const* sourceCode = R"( |