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>
|
#include <range/v3/view/transform.hpp>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
using namespace solidity::util;
|
||||||
using namespace solidity::langutil;
|
using namespace solidity::langutil;
|
||||||
using namespace solidity::frontend;
|
using namespace solidity::frontend;
|
||||||
|
|
||||||
@ -343,11 +344,18 @@ void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName)
|
|||||||
lengthValue = value->value;
|
lengthValue = value->value;
|
||||||
|
|
||||||
if (!lengthValue)
|
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(
|
m_errorReporter.typeError(
|
||||||
5462_error,
|
5462_error,
|
||||||
length->location(),
|
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)
|
else if (*lengthValue == 0)
|
||||||
m_errorReporter.typeError(1406_error, length->location(), "Array with zero length specified.");
|
m_errorReporter.typeError(1406_error, length->location(), "Array with zero length specified.");
|
||||||
else if (lengthValue->denominator() != 1)
|
else if (lengthValue->denominator() != 1)
|
||||||
|
@ -48,6 +48,8 @@
|
|||||||
#include <range/v3/view/enumerate.hpp>
|
#include <range/v3/view/enumerate.hpp>
|
||||||
#include <range/v3/view/zip.hpp>
|
#include <range/v3/view/zip.hpp>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -2192,8 +2194,177 @@ void TypeChecker::typeCheckFunctionCall(
|
|||||||
"\"staticcall\" is not supported by the VM version."
|
"\"staticcall\" is not supported by the VM version."
|
||||||
);
|
);
|
||||||
|
|
||||||
// Perform standard function call type checking
|
if (_functionCall.isSuffixCall())
|
||||||
typeCheckFunctionGeneralChecks(_functionCall, _functionType);
|
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)
|
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();
|
Type const* actualType = dynamic_cast<TypeType const&>(*expressionType).actualType();
|
||||||
solAssert(!!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())
|
if (actualType->containsNestedMapping())
|
||||||
m_errorReporter.fatalTypeError(
|
m_errorReporter.fatalTypeError(
|
||||||
@ -2948,7 +3125,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
|||||||
m_errorReporter.fatalTypeError(
|
m_errorReporter.fatalTypeError(
|
||||||
5704_error,
|
5704_error,
|
||||||
_functionCall.location(),
|
_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
|
// 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.
|
// is never reached. And, even if it was, SetOnce would trigger an assertion violation and not UB.
|
||||||
|
@ -95,6 +95,13 @@ private:
|
|||||||
FunctionTypePointer _functionType
|
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 typeCheckFallbackFunction(FunctionDefinition const& _function);
|
||||||
void typeCheckConstructor(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):
|
StringLiteralType::StringLiteralType(Literal const& _literal):
|
||||||
m_value(_literal.value())
|
m_value(_literal.value())
|
||||||
{
|
{
|
||||||
|
@ -621,6 +621,15 @@ public:
|
|||||||
/// @returns true if the value is not an integer.
|
/// @returns true if the value is not an integer.
|
||||||
bool isFractional() const { return m_value.denominator() != 1; }
|
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.
|
/// @returns true if the value is negative.
|
||||||
bool isNegative() const { return m_value < 0; }
|
bool isNegative() const { return m_value < 0; }
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user