diff --git a/libsolidity/analysis/DeclarationTypeChecker.cpp b/libsolidity/analysis/DeclarationTypeChecker.cpp index f17c854a7..746998bba 100644 --- a/libsolidity/analysis/DeclarationTypeChecker.cpp +++ b/libsolidity/analysis/DeclarationTypeChecker.cpp @@ -30,6 +30,7 @@ #include using namespace std; +using namespace solidity::util; using namespace solidity::langutil; using namespace solidity::frontend; @@ -343,11 +344,18 @@ void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName) lengthValue = value->value; if (!lengthValue) + { + string suffixErrorMessage; + if (auto const* functionCall = dynamic_cast(length)) + if (functionCall->isSuffixCall()) + suffixErrorMessage = " A suffixed literal is not a constant expression unless the suffix is a subdenomination."; + m_errorReporter.typeError( 5462_error, length->location(), - "Invalid array length, expected integer literal or constant expression." + "Invalid array length, expected integer literal or constant expression." + suffixErrorMessage ); + } else if (*lengthValue == 0) m_errorReporter.typeError(1406_error, length->location(), "Array with zero length specified."); else if (lengthValue->denominator() != 1) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index a202055c3..7fa7abab0 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -48,6 +48,8 @@ #include #include +#include + #include #include @@ -2192,8 +2194,177 @@ void TypeChecker::typeCheckFunctionCall( "\"staticcall\" is not supported by the VM version." ); - // Perform standard function call type checking - typeCheckFunctionGeneralChecks(_functionCall, _functionType); + if (_functionCall.isSuffixCall()) + typeCheckSuffixFunctionCall(_functionCall, _functionType); + else + // Perform standard function call type checking + typeCheckFunctionGeneralChecks(_functionCall, _functionType); +} + +/// Performs type checks on function calls performed via the suffix syntax. +void TypeChecker::typeCheckSuffixFunctionCall( + FunctionCall const& _functionCall, + FunctionTypePointer _functionType +) +{ + solAssert(_functionType); + solAssert(_functionCall.isSuffixCall()); + + vector> const& arguments = _functionCall.arguments(); + solAssert(arguments.size() == 1 && arguments[0]); + + auto const* literal = dynamic_cast(arguments[0].get()); + solAssert(literal); + + Type const* literalType = type(*literal); + solAssert(literalType); + + if ( + _functionType->hasBoundFirstArgument() || + !_functionType->hasDeclaration() || // Rejects function pointers, builtins, types and probably more + !dynamic_cast(&_functionType->declaration()) || // Rejects events and errors + !dynamic_cast(&_functionType->declaration())->usableAsSuffix() + ) + { + auto suffixDefinitionLocation = SecondarySourceLocation{}; + if ( + _functionType->hasDeclaration() && + dynamic_cast(&_functionType->declaration()) + ) + suffixDefinitionLocation.append( + "Suffix defined here:", + dynamic_cast(&_functionType->declaration())->location() + ); + + m_errorReporter.typeError( + 4438_error, + _functionCall.expression().location(), + suffixDefinitionLocation, + "The literal suffix must be either a subdenomination or a file-level suffix function." + ); + } + else + { + auto const* suffixDefinition = dynamic_cast(&_functionType->declaration()); + solAssert(suffixDefinition); + solAssert(!suffixDefinition->virtualSemantics()); + solAssert(!_functionType->takesArbitraryParameters()); + solAssert(_functionType->kind() == FunctionType::Kind::Internal); + + auto const* literalRationalType = dynamic_cast(literalType); + + optional parameterTypeMessage; + if (_functionType->parameterTypes().size() == 2) + { + if (!literalRationalType) + m_errorReporter.typeError( + 2505_error, + _functionCall.expression().location(), + SecondarySourceLocation().append( + "Suffix function defined here:", + dynamic_cast(&_functionType->declaration())->parameterList().location() + ), + "Functions that take 2 arguments can only be used as literal suffixes for rational numbers." + ); + else if ( + dynamic_cast(_functionType->parameterTypes()[0]) && + dynamic_cast(_functionType->parameterTypes()[1]) + ) + { + auto&& [mantissa, exponent] = literalRationalType->fractionalDecomposition(); + if (!mantissa || !exponent) + { + string mantissaOrExponentErrorMessage; + if (!mantissa && !exponent) + mantissaOrExponentErrorMessage = "The mantissa and the exponent are"; + else if (!exponent) + mantissaOrExponentErrorMessage = "The exponent is"; + else + mantissaOrExponentErrorMessage = "The mantissa is"; + + parameterTypeMessage = fmt::format( + "This number cannot be decomposed into a mantissa and decimal exponent " + "that fit the range of parameters of any possible suffix function. " + "{} out of range of the largest supported integer type.", + mantissaOrExponentErrorMessage + ); + } + else + { + vector mantissaOrExponentErrorMessages; + if (!mantissa->isImplicitlyConvertibleTo(*_functionType->parameterTypes()[0])) + mantissaOrExponentErrorMessages.emplace_back( + fmt::format( + "The mantissa is out of range of type {}.", + _functionType->parameterTypes()[0]->toString(true) + ) + ); + if (!exponent->isImplicitlyConvertibleTo(*_functionType->parameterTypes()[1])) + mantissaOrExponentErrorMessages.emplace_back( + fmt::format( + "The exponent is out of range of type {}.", + _functionType->parameterTypes()[1]->toString(true) + ) + ); + + if (!mantissaOrExponentErrorMessages.empty()) + parameterTypeMessage = + "This number cannot be decomposed into a mantissa and decimal exponent " + "that fit the range of parameters of the suffix function. " + + joinHumanReadable(mantissaOrExponentErrorMessages, " "); + } + } + else + // visit(FunctionDefinition) should have spotted if any of the parameters is not IntegerType + solAssert(m_errorReporter.hasErrors()); + } + else if (_functionType->parameterTypes().size() == 1) + { + if (!literalType->isImplicitlyConvertibleTo(*_functionType->parameterTypes()[0])) + { + string errorReason; + string literalDescription; + + if (literalRationalType) + literalDescription = "The number"; + else if (dynamic_cast(literalType)) + // Address literals and hex numbers are not easy to distinguish without counting the digits. + // The user may not even realize we see it as an address. Let's point that out in the message for clarity. + literalDescription = "The address"; + else + literalDescription = "The literal"; + + if ( + dynamic_cast(_functionType->parameterTypes()[0]) && + literalRationalType && + !literalRationalType->isFractional() + ) + errorReason = "is out of range of"; + else + errorReason = "cannot be converted to"; + + parameterTypeMessage = fmt::format( + "{} {} type {} accepted by the suffix function.", + literalDescription, + errorReason, + _functionType->parameterTypes()[0]->toString(true) + ); + } + } + else + solAssert(m_errorReporter.hasErrors()); + + if (parameterTypeMessage.has_value()) + m_errorReporter.typeError( + 8838_error, + literal->location(), + SecondarySourceLocation().append( + "Suffix function defined here:", + dynamic_cast(&_functionType->declaration())->parameterList().location() + ), + parameterTypeMessage.value() + ); + } } void TypeChecker::typeCheckFallbackFunction(FunctionDefinition const& _function) @@ -2916,7 +3087,13 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) Type const* actualType = dynamic_cast(*expressionType).actualType(); solAssert(!!actualType, ""); - if (actualType->category() == Type::Category::Struct) + if (_functionCall.isSuffixCall()) + m_errorReporter.fatalTypeError( + 6469_error, + _functionCall.location(), + "Type cannot be used as a literal suffix." + ); + else if (actualType->category() == Type::Category::Struct) { if (actualType->containsNestedMapping()) m_errorReporter.fatalTypeError( @@ -2948,7 +3125,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) m_errorReporter.fatalTypeError( 5704_error, _functionCall.location(), - capitalized(Type::categoryName(expressionType->category())) + " is not callable." + capitalized(Type::categoryName(expressionType->category())) + + (_functionCall.isSuffixCall() ? " cannot be used as a literal suffix." : " is not callable.") ); // Unreachable, because fatalTypeError throws. We don't set kind, but that's okay because the switch below // is never reached. And, even if it was, SetOnce would trigger an assertion violation and not UB. diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 2ef6b818b..fb33f51f4 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -95,6 +95,13 @@ private: FunctionTypePointer _functionType ); + /// Performs type checks on function calls performed via the suffix syntax. + /// Must not be called before the suffixed literal is visited. + void typeCheckSuffixFunctionCall( + FunctionCall const& _functionCall, + FunctionTypePointer _functionType + ); + void typeCheckFallbackFunction(FunctionDefinition const& _function); void typeCheckConstructor(FunctionDefinition const& _function); diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index c2356ad81..a39c0cba9 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1292,6 +1292,42 @@ FixedPointType const* RationalNumberType::fixedPointType() const ); } +pair RationalNumberType::fractionalDecomposition() const +{ + rational const maxUint = rational((bigint(1) << 256) - 1); + rational const minInt = -rational(bigint(1) << 255); + + bool negative = (m_value < 0); + rational const maxMantissa = (negative ? -minInt : maxUint); + + rational negatedExponent = 0; + rational unsignedMantissa = abs(m_value); + + if (unsignedMantissa > maxMantissa) + return {nullptr, TypeProvider::rationalNumber(0)}; + + while (unsignedMantissa.denominator() != 1) + { + unsignedMantissa *= 10; + ++negatedExponent; + + // NOTE: Technically RationalNumberType::isValidLiteral() should not allow an exponent so + // large we cannot store it in 4096 bits. For some reason it does not validate numbers not + // in scientific notation though. See https://github.com/ethereum/solidity/issues/14100 + if (negatedExponent > maxUint && unsignedMantissa > maxMantissa) + return {nullptr, nullptr}; + if (negatedExponent > maxUint) + return {TypeProvider::rationalNumber(unsignedMantissa), nullptr}; + if (unsignedMantissa > maxMantissa) + return {nullptr, TypeProvider::rationalNumber(negatedExponent)}; + } + + return { + TypeProvider::rationalNumber(unsignedMantissa), + TypeProvider::rationalNumber(negatedExponent), + }; +} + StringLiteralType::StringLiteralType(Literal const& _literal): m_value(_literal.value()) { diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 9c6389a29..1df9da244 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -621,6 +621,15 @@ public: /// @returns true if the value is not an integer. bool isFractional() const { return m_value.denominator() != 1; } + /// Tries to decompose a rational number into two integers - a mantissa and a non-negative + /// base-10 exponent, such that the number is equal to `mantissa * 10**-exponent`. + /// Note that exponent will be non-zero if and only if the number is fractional. + /// @returns Pair of non-null pointers representing the types of the literals corresponding to + /// mantissa and exponent if the resulting mantissa and exponent both fit in 256 bits. + /// If either of these values does not fit in 256 bits, its value in the pair is null. + /// Otherwise, Pair of null pointers. + std::pair fractionalDecomposition() const; + /// @returns true if the value is negative. bool isNegative() const { return m_value < 0; }