From b7a9daa2f8cfff09ea2ed22c72774cf874d2ca22 Mon Sep 17 00:00:00 2001 From: nishant-sachdeva Date: Tue, 22 Feb 2022 23:13:41 +0530 Subject: [PATCH] Generate warning when rational numbers are converted to their mobile type without explicit requests --- docs/types/operators.rst | 17 ++++++++++ docs/types/value-types.rst | 11 ++++++- ...y_operator_with_literal_types_overflow.sol | 19 +++++++++++ ...tor_return_type_with_literal_arguments.sol | 33 +++++++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 test/libsolidity/semanticTests/literals/ternary_operator_with_literal_types_overflow.sol create mode 100644 test/libsolidity/syntaxTests/literals/ternary_operator_return_type_with_literal_arguments.sol diff --git a/docs/types/operators.rst b/docs/types/operators.rst index 450963e11..be5f0d456 100644 --- a/docs/types/operators.rst +++ b/docs/types/operators.rst @@ -26,6 +26,23 @@ except for comparison operators where the result is always ``bool``. The operators ``**`` (exponentiation), ``<<`` and ``>>`` use the type of the left operand for the operation and the result. +Ternary Operator +---------------- +The ternary operator is used in expressions of the form `` ? : ``. +It evaluates one of the latter two given expressions depending upon the result of the evaluation of the main ````. +If ```` evaluates to ``true``, then ```` will be evaluated, otherwise ```` is evaluated. + +The result of the ternary operator does not have a rational number type, even if all of its operands are rational number literals. +The result type is determined from the types of the two operands in the same way as above, converting to their mobile type first if required. + +As a consequence, ``255 + (true ? 1 : 0)`` will revert due to arithmetic overflow. +The reason is that ``(true ? 1 : 0)`` is of ``uint8`` type, which forces the addition to be performed in ``uint8`` as well, +and 256 exceeds the range allowed for this type. + +Another consequence is that an expression like ``1.5 + 1.5`` is valid but ``1.5 + (true ? 1.5 : 2.5)`` is not. +This is because the former is a rational expression evaluated in unlimited precision and only its final value matters. +The latter involves a conversion of a fractional rational number to an integer, which is currently disallowed. + .. index:: assignment, lvalue, ! compound operators Compound and Increment/Decrement Operators diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 0d6a30cd6..8f1009407 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -463,7 +463,7 @@ There is no additional semantic meaning added to a number literal containing und the underscores are ignored. 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 or by explicit conversion). +using them together with anything else than a number literal expression (like boolean literals) or by explicit conversion). This means that computations do not overflow and divisions do not truncate in number literal expressions. @@ -471,6 +471,15 @@ For example, ``(2**800 + 1) - 2**800`` results in the constant ``1`` (of type `` although intermediate results would not even fit the machine word size. Furthermore, ``.5 * 8`` results in the integer ``4`` (although non-integers were used in between). +.. warning:: + While most operators produce a literal expression when applied to literals, there are certain operators that do not follow this pattern: + + - Ternary operator (``... ? ... : ...``), + - Array subscript (``[]``). + + You might expect expressions like ``255 + (true ? 1 : 0)`` or ``255 + [1, 2, 3][0]`` to be equivalent to using the literal 256 + directly, but in fact they are computed within the type ``uint8`` and can overflow. + 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 diff --git a/test/libsolidity/semanticTests/literals/ternary_operator_with_literal_types_overflow.sol b/test/libsolidity/semanticTests/literals/ternary_operator_with_literal_types_overflow.sol new file mode 100644 index 000000000..22d61c6c5 --- /dev/null +++ b/test/libsolidity/semanticTests/literals/ternary_operator_with_literal_types_overflow.sol @@ -0,0 +1,19 @@ +contract TestTernary +{ + function h() pure public returns (uint16 b) + { + b = (true ? 63 : 255) + (false ? 63 : 255); + } + + function g() pure public returns (uint16 a) + { + bool t = true; + bool f = false; + a = (t ? 63 : 255) + (f ? 63 : 255); + } +} +// ==== +// compileViaYul: also +// ---- +// g() -> FAILURE, hex"4e487b71", 0x11 +// h() -> FAILURE, hex"4e487b71", 0x11 diff --git a/test/libsolidity/syntaxTests/literals/ternary_operator_return_type_with_literal_arguments.sol b/test/libsolidity/syntaxTests/literals/ternary_operator_return_type_with_literal_arguments.sol new file mode 100644 index 000000000..6a928c3ad --- /dev/null +++ b/test/libsolidity/syntaxTests/literals/ternary_operator_return_type_with_literal_arguments.sol @@ -0,0 +1,33 @@ +contract TestTernary +{ + function g() pure public + { + bool t = true; + bool f = false; + uint8 v255 = 255; + uint8 v63 = 63; + uint8 a; + + // Currently none of these should produce errors or warnings. + // The result of the operator is always a limited-precision integer, even if all arguments are literals. + + a = (t ? 63 : 255) + (f ? 63 : 255); + a = (t ? 0x3f : 0xff) + (f ? 0x3f : 0xff); + a = (t ? uint8(63) : 255) + (f ? 63 : uint8(255)); + a = (t ? v63 : 255) + (f ? 63 : v255); + + a = (true ? 63 : 255) + (false ? 63 : 255); + a = (true ? 0x3f : 0xff) + (false ? 0x3f : 0xff); + a = (true ? uint8(63) : 255) + (false ? 63 : uint8(255)); + a = (true ? v63 : 255) + (false ? 63 : v255); + + a = (t ? 63 : 255) - (f ? 63 : 255); + a = (t ? 63 : 255) * (f ? 63 : 255); + a = (t ? 63 : 255) / (f ? 63 : 255); + + a = (t ? (true ? 63 : 255) : (false ? 63 : 255)) + (f ? (t ? 63 : 255) : (f ? 63 : 255)); + a = uint8(t ? 63 : 255) + uint8(f ? 63 : 255); + + } +} +// ----