From b4380a21372c62833eae3bda39de06179b41068f Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Tue, 9 Apr 2019 15:30:54 +0200 Subject: [PATCH] Move convert functions to Yul module --- libsolidity/codegen/ABIFunctions.cpp | 312 +---------------------- libsolidity/codegen/ABIFunctions.h | 21 -- libsolidity/codegen/YulUtilFunctions.cpp | 302 ++++++++++++++++++++++ libsolidity/codegen/YulUtilFunctions.h | 21 ++ 4 files changed, 328 insertions(+), 328 deletions(-) diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 41ec073f0..759bf2bc1 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -255,143 +255,6 @@ string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const return suffix; } -string ABIFunctions::cleanupFunction(Type const& _type) -{ - string functionName = string("cleanup_") + _type.identifier(); - return createFunction(functionName, [&]() { - Whiskers templ(R"( - function (value) -> cleaned { - - } - )"); - templ("functionName", functionName); - switch (_type.category()) - { - case Type::Category::Address: - templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)"); - break; - case Type::Category::Integer: - { - IntegerType const& type = dynamic_cast(_type); - if (type.numBits() == 256) - templ("body", "cleaned := value"); - else if (type.isSigned()) - templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)"); - else - templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")"); - break; - } - case Type::Category::RationalNumber: - templ("body", "cleaned := value"); - break; - case Type::Category::Bool: - templ("body", "cleaned := iszero(iszero(value))"); - break; - case Type::Category::FixedPoint: - solUnimplemented("Fixed point types not implemented."); - break; - case Type::Category::Function: - solAssert(dynamic_cast(_type).kind() == FunctionType::Kind::External, ""); - templ("body", "cleaned := " + cleanupFunction(FixedBytesType(24)) + "(value)"); - break; - case Type::Category::Array: - case Type::Category::Struct: - case Type::Category::Mapping: - solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type."); - templ("body", "cleaned := value"); - break; - case Type::Category::FixedBytes: - { - FixedBytesType const& type = dynamic_cast(_type); - if (type.numBytes() == 32) - templ("body", "cleaned := value"); - else if (type.numBytes() == 0) - // This is disallowed in the type system. - solAssert(false, ""); - else - { - size_t numBits = type.numBytes() * 8; - u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits); - templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")"); - } - break; - } - case Type::Category::Contract: - { - AddressType addressType(dynamic_cast(_type).isPayable() ? - StateMutability::Payable : - StateMutability::NonPayable - ); - templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)"); - break; - } - case Type::Category::Enum: - { - // Out of range enums cannot be truncated unambigiously and therefore it should be an error. - templ("body", "cleaned := value " + validatorFunction(_type) + "(value)"); - break; - } - case Type::Category::InaccessibleDynamic: - templ("body", "cleaned := 0"); - break; - default: - solAssert(false, "Cleanup of type " + _type.identifier() + " requested."); - } - - return templ.render(); - }); -} - -string ABIFunctions::validatorFunction(Type const& _type, bool _revertOnFailure) -{ - string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); - return createFunction(functionName, [&]() { - Whiskers templ(R"( - function (value) { - if iszero() { } - } - )"); - templ("functionName", functionName); - if (_revertOnFailure) - templ("failure", "revert(0, 0)"); - else - templ("failure", "invalid()"); - - switch (_type.category()) - { - case Type::Category::Address: - case Type::Category::Integer: - case Type::Category::RationalNumber: - case Type::Category::Bool: - case Type::Category::FixedPoint: - case Type::Category::Function: - case Type::Category::Array: - case Type::Category::Struct: - case Type::Category::Mapping: - case Type::Category::FixedBytes: - case Type::Category::Contract: - { - templ("condition", "eq(value, " + cleanupFunction(_type) + "(value))"); - break; - } - case Type::Category::Enum: - { - size_t members = dynamic_cast(_type).numberOfMembers(); - solAssert(members > 0, "empty enum should have caused a parser error."); - templ("condition", "lt(value, " + to_string(members) + ")"); - break; - } - case Type::Category::InaccessibleDynamic: - templ("condition", "1"); - break; - default: - solAssert(false, "Validation of type " + _type.identifier() + " requested."); - } - - return templ.render(); - }); -} - string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes) { solAssert(_type.isValueType(), ""); @@ -425,171 +288,6 @@ string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFu }); } -string ABIFunctions::conversionFunction(Type const& _from, Type const& _to) -{ - string functionName = - "convert_" + - _from.identifier() + - "_to_" + - _to.identifier(); - return createFunction(functionName, [&]() { - Whiskers templ(R"( - function (value) -> converted { - - } - )"); - templ("functionName", functionName); - string body; - auto toCategory = _to.category(); - auto fromCategory = _from.category(); - switch (fromCategory) - { - case Type::Category::Address: - body = - Whiskers("converted := (value)") - ("convert", conversionFunction(IntegerType(160), _to)) - .render(); - break; - case Type::Category::Integer: - case Type::Category::RationalNumber: - case Type::Category::Contract: - { - if (RationalNumberType const* rational = dynamic_cast(&_from)) - solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType."); - if (toCategory == Type::Category::FixedBytes) - { - solAssert( - fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber, - "Invalid conversion to FixedBytesType requested." - ); - FixedBytesType const& toBytesType = dynamic_cast(_to); - body = - Whiskers("converted := ((value))") - ("shiftLeft", m_utils.shiftLeftFunction(256 - toBytesType.numBytes() * 8)) - ("clean", cleanupFunction(_from)) - .render(); - } - else if (toCategory == Type::Category::Enum) - { - solAssert(_from.mobileType(), ""); - body = - Whiskers("converted := ((value))") - ("cleanEnum", cleanupFunction(_to)) - // "mobileType()" returns integer type for rational - ("cleanInt", cleanupFunction(*_from.mobileType())) - .render(); - } - else if (toCategory == Type::Category::FixedPoint) - solUnimplemented("Not yet implemented - FixedPointType."); - else if (toCategory == Type::Category::Address) - body = - Whiskers("converted := (value)") - ("convert", conversionFunction(_from, IntegerType(160))) - .render(); - else - { - solAssert( - toCategory == Type::Category::Integer || - toCategory == Type::Category::Contract, - ""); - IntegerType const addressType(160); - IntegerType const& to = - toCategory == Type::Category::Integer ? - dynamic_cast(_to) : - addressType; - - // Clean according to the "to" type, except if this is - // a widening conversion. - IntegerType const* cleanupType = &to; - if (fromCategory != Type::Category::RationalNumber) - { - IntegerType const& from = - fromCategory == Type::Category::Integer ? - dynamic_cast(_from) : - addressType; - if (to.numBits() > from.numBits()) - cleanupType = &from; - } - body = - Whiskers("converted := (value)") - ("cleanInt", cleanupFunction(*cleanupType)) - .render(); - } - break; - } - case Type::Category::Bool: - { - solAssert(_from == _to, "Invalid conversion for bool."); - body = - Whiskers("converted := (value)") - ("clean", cleanupFunction(_from)) - .render(); - break; - } - case Type::Category::FixedPoint: - solUnimplemented("Fixed point types not implemented."); - break; - case Type::Category::Array: - solUnimplementedAssert(false, "Array conversion not implemented."); - break; - case Type::Category::Struct: - solUnimplementedAssert(false, "Struct conversion not implemented."); - break; - case Type::Category::FixedBytes: - { - FixedBytesType const& from = dynamic_cast(_from); - if (toCategory == Type::Category::Integer) - body = - Whiskers("converted := ((value))") - ("shift", m_utils.shiftRightFunction(256 - from.numBytes() * 8)) - ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) - .render(); - else if (toCategory == Type::Category::Address) - body = - Whiskers("converted := (value)") - ("convert", conversionFunction(_from, IntegerType(160))) - .render(); - else - { - // clear for conversion to longer bytes - solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); - body = - Whiskers("converted := (value)") - ("clean", cleanupFunction(from)) - .render(); - } - break; - } - case Type::Category::Function: - { - solAssert(false, "Conversion should not be called for function types."); - break; - } - case Type::Category::Enum: - { - solAssert(toCategory == Type::Category::Integer || _from == _to, ""); - EnumType const& enumType = dynamic_cast(_from); - body = - Whiskers("converted := (value)") - ("clean", cleanupFunction(enumType)) - .render(); - break; - } - case Type::Category::Tuple: - { - solUnimplementedAssert(false, "Tuple conversion not implemented."); - break; - } - default: - solAssert(false, ""); - } - - solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName()); - templ("body", body); - return templ.render(); - }); -} - string ABIFunctions::abiEncodingFunction( Type const& _from, Type const& _to, @@ -678,9 +376,9 @@ string ABIFunctions::abiEncodingFunction( { string cleanupConvert; if (_from == to) - cleanupConvert = cleanupFunction(_from) + "(value)"; + cleanupConvert = m_utils.cleanupFunction(_from) + "(value)"; else - cleanupConvert = conversionFunction(_from, to) + "(value)"; + cleanupConvert = m_utils.conversionFunction(_from, to) + "(value)"; if (!_options.padded) cleanupConvert = m_utils.leftAlignFunction(to) + "(" + cleanupConvert + ")"; templ("cleanupConvert", cleanupConvert); @@ -1365,7 +1063,7 @@ string ABIFunctions::abiEncodingFunctionFunctionType( } )") ("functionName", functionName) - ("cleanExtFun", cleanupFunction(_to)) + ("cleanExtFun", m_utils.cleanupFunction(_to)) .render(); }); } @@ -1431,7 +1129,7 @@ string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromM templ("load", _fromMemory ? "mload" : "calldataload"); // Validation should use the type and not decodingType, because e.g. // the decoding type of an enum is a plain int. - templ("validator", validatorFunction(_type, true)); + templ("validator", m_utils.validatorFunction(_type, true)); return templ.render(); }); @@ -1696,7 +1394,7 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, )") ("functionName", functionName) ("load", _fromMemory ? "mload" : "calldataload") - ("validateExtFun", validatorFunction(_type, true)) + ("validateExtFun", m_utils.validatorFunction(_type, true)) .render(); } }); diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index 272ca3607..fe72248c2 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -128,20 +128,6 @@ private: std::string toFunctionNameSuffix() const; }; - /// @returns the name of the cleanup function for the given type and - /// adds its implementation to the requested functions. - /// The cleanup function defers to the validator function with "assert" - /// if there is no reasonable way to clean a value. - std::string cleanupFunction(Type const& _type); - - /// @returns the name of the validator function for the given type and - /// adds its implementation to the requested functions. - /// @param _revertOnFailure if true, causes revert on invalid data, - /// otherwise an assertion failure. - /// - /// This is used for data decoded from external sources. - std::string validatorFunction(Type const& _type, bool _revertOnFailure = false); - /// Performs cleanup after reading from a potentially compressed storage slot. /// The function does not perform any validation, it just masks or sign-extends /// higher order bytes or left-aligns (in case of bytesNN). @@ -151,13 +137,6 @@ private: /// single variable. std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes); - /// @returns the name of the function that converts a value of type @a _from - /// to a value of type @a _to. The resulting vale is guaranteed to be in range - /// (i.e. "clean"). Asserts on failure. - /// - /// This is used for data being encoded or general type conversions in the code. - std::string conversionFunction(Type const& _from, Type const& _to); - /// @returns the name of the ABI encoding function with the given type /// and queues the generation of the function to the requested functions. /// @param _fromStack if false, the input value was just loaded from storage diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 43596189a..9b49fd1eb 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -450,6 +450,308 @@ string YulUtilFunctions::allocationFunction() }); } +string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) +{ + string functionName = + "convert_" + + _from.identifier() + + "_to_" + + _to.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function (value) -> converted { + + } + )"); + templ("functionName", functionName); + string body; + auto toCategory = _to.category(); + auto fromCategory = _from.category(); + switch (fromCategory) + { + case Type::Category::Address: + body = + Whiskers("converted := (value)") + ("convert", conversionFunction(IntegerType(160), _to)) + .render(); + break; + case Type::Category::Integer: + case Type::Category::RationalNumber: + case Type::Category::Contract: + { + if (RationalNumberType const* rational = dynamic_cast(&_from)) + solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType."); + if (toCategory == Type::Category::FixedBytes) + { + solAssert( + fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber, + "Invalid conversion to FixedBytesType requested." + ); + FixedBytesType const& toBytesType = dynamic_cast(_to); + body = + Whiskers("converted := ((value))") + ("shiftLeft", shiftLeftFunction(256 - toBytesType.numBytes() * 8)) + ("clean", cleanupFunction(_from)) + .render(); + } + else if (toCategory == Type::Category::Enum) + { + solAssert(_from.mobileType(), ""); + body = + Whiskers("converted := ((value))") + ("cleanEnum", cleanupFunction(_to)) + // "mobileType()" returns integer type for rational + ("cleanInt", cleanupFunction(*_from.mobileType())) + .render(); + } + else if (toCategory == Type::Category::FixedPoint) + solUnimplemented("Not yet implemented - FixedPointType."); + else if (toCategory == Type::Category::Address) + body = + Whiskers("converted := (value)") + ("convert", conversionFunction(_from, IntegerType(160))) + .render(); + else + { + solAssert( + toCategory == Type::Category::Integer || + toCategory == Type::Category::Contract, + ""); + IntegerType const addressType(160); + IntegerType const& to = + toCategory == Type::Category::Integer ? + dynamic_cast(_to) : + addressType; + + // Clean according to the "to" type, except if this is + // a widening conversion. + IntegerType const* cleanupType = &to; + if (fromCategory != Type::Category::RationalNumber) + { + IntegerType const& from = + fromCategory == Type::Category::Integer ? + dynamic_cast(_from) : + addressType; + if (to.numBits() > from.numBits()) + cleanupType = &from; + } + body = + Whiskers("converted := (value)") + ("cleanInt", cleanupFunction(*cleanupType)) + .render(); + } + break; + } + case Type::Category::Bool: + { + solAssert(_from == _to, "Invalid conversion for bool."); + body = + Whiskers("converted := (value)") + ("clean", cleanupFunction(_from)) + .render(); + break; + } + case Type::Category::FixedPoint: + solUnimplemented("Fixed point types not implemented."); + break; + case Type::Category::Array: + solUnimplementedAssert(false, "Array conversion not implemented."); + break; + case Type::Category::Struct: + solUnimplementedAssert(false, "Struct conversion not implemented."); + break; + case Type::Category::FixedBytes: + { + FixedBytesType const& from = dynamic_cast(_from); + if (toCategory == Type::Category::Integer) + body = + Whiskers("converted := ((value))") + ("shift", shiftRightFunction(256 - from.numBytes() * 8)) + ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) + .render(); + else if (toCategory == Type::Category::Address) + body = + Whiskers("converted := (value)") + ("convert", conversionFunction(_from, IntegerType(160))) + .render(); + else + { + // clear for conversion to longer bytes + solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); + body = + Whiskers("converted := (value)") + ("clean", cleanupFunction(from)) + .render(); + } + break; + } + case Type::Category::Function: + { + solAssert(false, "Conversion should not be called for function types."); + break; + } + case Type::Category::Enum: + { + solAssert(toCategory == Type::Category::Integer || _from == _to, ""); + EnumType const& enumType = dynamic_cast(_from); + body = + Whiskers("converted := (value)") + ("clean", cleanupFunction(enumType)) + .render(); + break; + } + case Type::Category::Tuple: + { + solUnimplementedAssert(false, "Tuple conversion not implemented."); + break; + } + default: + solAssert(false, ""); + } + + solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName()); + templ("body", body); + return templ.render(); + }); +} + +string YulUtilFunctions::cleanupFunction(Type const& _type) +{ + string functionName = string("cleanup_") + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function (value) -> cleaned { + + } + )"); + templ("functionName", functionName); + switch (_type.category()) + { + case Type::Category::Address: + templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)"); + break; + case Type::Category::Integer: + { + IntegerType const& type = dynamic_cast(_type); + if (type.numBits() == 256) + templ("body", "cleaned := value"); + else if (type.isSigned()) + templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)"); + else + templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")"); + break; + } + case Type::Category::RationalNumber: + templ("body", "cleaned := value"); + break; + case Type::Category::Bool: + templ("body", "cleaned := iszero(iszero(value))"); + break; + case Type::Category::FixedPoint: + solUnimplemented("Fixed point types not implemented."); + break; + case Type::Category::Function: + solAssert(dynamic_cast(_type).kind() == FunctionType::Kind::External, ""); + templ("body", "cleaned := " + cleanupFunction(FixedBytesType(24)) + "(value)"); + break; + case Type::Category::Array: + case Type::Category::Struct: + case Type::Category::Mapping: + solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type."); + templ("body", "cleaned := value"); + break; + case Type::Category::FixedBytes: + { + FixedBytesType const& type = dynamic_cast(_type); + if (type.numBytes() == 32) + templ("body", "cleaned := value"); + else if (type.numBytes() == 0) + // This is disallowed in the type system. + solAssert(false, ""); + else + { + size_t numBits = type.numBytes() * 8; + u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits); + templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")"); + } + break; + } + case Type::Category::Contract: + { + AddressType addressType(dynamic_cast(_type).isPayable() ? + StateMutability::Payable : + StateMutability::NonPayable + ); + templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)"); + break; + } + case Type::Category::Enum: + { + // Out of range enums cannot be truncated unambigiously and therefore it should be an error. + templ("body", "cleaned := value " + validatorFunction(_type) + "(value)"); + break; + } + case Type::Category::InaccessibleDynamic: + templ("body", "cleaned := 0"); + break; + default: + solAssert(false, "Cleanup of type " + _type.identifier() + " requested."); + } + + return templ.render(); + }); +} + +string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure) +{ + string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function (value) { + if iszero() { } + } + )"); + templ("functionName", functionName); + if (_revertOnFailure) + templ("failure", "revert(0, 0)"); + else + templ("failure", "invalid()"); + + switch (_type.category()) + { + case Type::Category::Address: + case Type::Category::Integer: + case Type::Category::RationalNumber: + case Type::Category::Bool: + case Type::Category::FixedPoint: + case Type::Category::Function: + case Type::Category::Array: + case Type::Category::Struct: + case Type::Category::Mapping: + case Type::Category::FixedBytes: + case Type::Category::Contract: + { + templ("condition", "eq(value, " + cleanupFunction(_type) + "(value))"); + break; + } + case Type::Category::Enum: + { + size_t members = dynamic_cast(_type).numberOfMembers(); + solAssert(members > 0, "empty enum should have caused a parser error."); + templ("condition", "lt(value, " + to_string(members) + ")"); + break; + } + case Type::Category::InaccessibleDynamic: + templ("condition", "1"); + break; + default: + solAssert(false, "Validation of type " + _type.identifier() + " requested."); + } + + return templ.render(); + }); +} + string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix) { string result; diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index cffa9efbc..8a5b2e2c9 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -92,6 +92,27 @@ public: /// Return value: pointer std::string allocationFunction(); + /// @returns the name of the function that converts a value of type @a _from + /// to a value of type @a _to. The resulting vale is guaranteed to be in range + /// (i.e. "clean"). Asserts on failure. + /// + /// This is used for data being encoded or general type conversions in the code. + std::string conversionFunction(Type const& _from, Type const& _to); + + /// @returns the name of the cleanup function for the given type and + /// adds its implementation to the requested functions. + /// The cleanup function defers to the validator function with "assert" + /// if there is no reasonable way to clean a value. + std::string cleanupFunction(Type const& _type); + + /// @returns the name of the validator function for the given type and + /// adds its implementation to the requested functions. + /// @param _revertOnFailure if true, causes revert on invalid data, + /// otherwise an assertion failure. + /// + /// This is used for data decoded from external sources. + std::string validatorFunction(Type const& _type, bool _revertOnFailure = false); + /// @returns a string containing a comma-separated list of variable names consisting of @a _baseName suffixed /// with increasing integers in the range [@a _startSuffix, @a _endSuffix), if @a _startSuffix < @a _endSuffix, /// and with decreasing integers in the range [@a _endSuffix, @a _startSuffix), if @a _endSuffix < @a _startSuffix.