mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
User-defined literal suffixes: Analysis
This commit is contained in:
parent
f97e3884cb
commit
3cbbfc890f
@ -30,6 +30,7 @@
|
||||
#include <range/v3/view/transform.hpp>
|
||||
|
||||
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<FunctionCall const*>(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)
|
||||
|
@ -48,6 +48,8 @@
|
||||
#include <range/v3/view/enumerate.hpp>
|
||||
#include <range/v3/view/zip.hpp>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@ -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<ASTPointer<Expression const>> const& arguments = _functionCall.arguments();
|
||||
solAssert(arguments.size() == 1 && arguments[0]);
|
||||
|
||||
auto const* literal = dynamic_cast<Literal const*>(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<FunctionDefinition const*>(&_functionType->declaration()) || // Rejects events and errors
|
||||
!dynamic_cast<FunctionDefinition const*>(&_functionType->declaration())->usableAsSuffix()
|
||||
)
|
||||
{
|
||||
auto suffixDefinitionLocation = SecondarySourceLocation{};
|
||||
if (
|
||||
_functionType->hasDeclaration() &&
|
||||
dynamic_cast<Declaration const*>(&_functionType->declaration())
|
||||
)
|
||||
suffixDefinitionLocation.append(
|
||||
"Suffix defined here:",
|
||||
dynamic_cast<Declaration const*>(&_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<FunctionDefinition const*>(&_functionType->declaration());
|
||||
solAssert(suffixDefinition);
|
||||
solAssert(!suffixDefinition->virtualSemantics());
|
||||
solAssert(!_functionType->takesArbitraryParameters());
|
||||
solAssert(_functionType->kind() == FunctionType::Kind::Internal);
|
||||
|
||||
auto const* literalRationalType = dynamic_cast<RationalNumberType const*>(literalType);
|
||||
|
||||
optional<string> parameterTypeMessage;
|
||||
if (_functionType->parameterTypes().size() == 2)
|
||||
{
|
||||
if (!literalRationalType)
|
||||
m_errorReporter.typeError(
|
||||
2505_error,
|
||||
_functionCall.expression().location(),
|
||||
SecondarySourceLocation().append(
|
||||
"Suffix function defined here:",
|
||||
dynamic_cast<FunctionDefinition const*>(&_functionType->declaration())->parameterList().location()
|
||||
),
|
||||
"Functions that take 2 arguments can only be used as literal suffixes for rational numbers."
|
||||
);
|
||||
else if (
|
||||
dynamic_cast<IntegerType const*>(_functionType->parameterTypes()[0]) &&
|
||||
dynamic_cast<IntegerType const*>(_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<string> 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<AddressType const*>(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<IntegerType const*>(_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<FunctionDefinition const*>(&_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<TypeType const&>(*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.
|
||||
|
@ -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);
|
||||
|
||||
|
@ -1292,6 +1292,42 @@ FixedPointType const* RationalNumberType::fixedPointType() const
|
||||
);
|
||||
}
|
||||
|
||||
pair<RationalNumberType const*, RationalNumberType const*> 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())
|
||||
{
|
||||
|
@ -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<RationalNumberType const*, RationalNumberType const*> fractionalDecomposition() const;
|
||||
|
||||
/// @returns true if the value is negative.
|
||||
bool isNegative() const { return m_value < 0; }
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user