Merge pull request #9038 from ethereum/develop

Merge develop into breaking.
This commit is contained in:
chriseth
2020-05-27 15:24:27 +02:00
committed by GitHub
116 changed files with 1424 additions and 582 deletions
@@ -41,8 +41,8 @@ bool hasEqualNameAndParameters(T const& _a, B const& _b)
{
return
_a.name() == _b.name() &&
FunctionType(_a).asCallableFunction(false)->hasEqualParameterTypes(
*FunctionType(_b).asCallableFunction(false)
FunctionType(_a).asExternallyCallableFunction(false)->hasEqualParameterTypes(
*FunctionType(_b).asExternallyCallableFunction(false)
);
}
@@ -345,7 +345,7 @@ void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _c
// under non error circumstances this should be true
if (functionType->interfaceFunctionType())
externalDeclarations[functionType->externalSignature()].emplace_back(
f, functionType->asCallableFunction(false)
f, functionType->asExternallyCallableFunction(false)
);
}
for (VariableDeclaration const* v: contract->stateVariables())
@@ -355,7 +355,7 @@ void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _c
// under non error circumstances this should be true
if (functionType->interfaceFunctionType())
externalDeclarations[functionType->externalSignature()].emplace_back(
v, functionType->asCallableFunction(false)
v, functionType->asExternallyCallableFunction(false)
);
}
}
+8 -2
View File
@@ -94,7 +94,10 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
case VariableOccurrence::Kind::Return:
if (unassignedVariables.count(&variableOccurrence.declaration()))
{
if (variableOccurrence.declaration().type()->dataStoredIn(DataLocation::Storage))
if (
variableOccurrence.declaration().type()->dataStoredIn(DataLocation::Storage) ||
variableOccurrence.declaration().type()->dataStoredIn(DataLocation::CallData)
)
// Merely store the unassigned access. We do not generate an error right away, since this
// path might still always revert. It is only an error if this is propagated to the exit
// node of the function (i.e. there is a path with an uninitialized access).
@@ -135,13 +138,16 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
if (variableOccurrence->occurrence())
ssl.append("The variable was declared here.", variableOccurrence->declaration().location());
bool isStorage = variableOccurrence->declaration().type()->dataStoredIn(DataLocation::Storage);
m_errorReporter.typeError(
3464_error,
variableOccurrence->occurrence() ?
*variableOccurrence->occurrence() :
variableOccurrence->declaration().location(),
ssl,
string("This variable is of storage pointer type and can be ") +
"This variable is of " +
string(isStorage ? "storage" : "calldata") +
" pointer type and can be " +
(variableOccurrence->kind() == VariableOccurrence::Kind::Return ? "returned" : "accessed") +
" without prior assignment, which would lead to undefined behaviour."
);
@@ -151,7 +151,7 @@ void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
{
_typeName.annotation().type = TypeProvider::emptyTuple();
m_errorReporter.fatalTypeError(
9755_error,
5172_error,
_typeName.location(),
"Name has to refer to a struct, enum or contract."
);
@@ -176,7 +176,7 @@ bool DeclarationTypeChecker::visit(FunctionTypeName const& _typeName)
break;
default:
m_errorReporter.fatalTypeError(
7653_error,
6012_error,
_typeName.location(),
"Invalid visibility, can only be \"external\" or \"internal\"."
);
@@ -186,7 +186,7 @@ bool DeclarationTypeChecker::visit(FunctionTypeName const& _typeName)
if (_typeName.isPayable() && _typeName.visibility() != Visibility::External)
{
m_errorReporter.fatalTypeError(
6138_error,
7415_error,
_typeName.location(),
"Only external function types can be payable."
);
@@ -246,7 +246,7 @@ void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName)
}
if (baseType->storageBytes() == 0)
m_errorReporter.fatalTypeError(
9390_error,
6493_error,
_typeName.baseType().location(),
"Illegal base type of storage size zero for array."
);
@@ -259,16 +259,16 @@ void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName)
u256 lengthValue = 0;
if (!lengthType || !lengthType->mobileType())
m_errorReporter.typeError(
8922_error,
5462_error,
length->location(),
"Invalid array length, expected integer literal or constant expression."
);
else if (lengthType->isZero())
m_errorReporter.typeError(1220_error, length->location(), "Array with zero length specified.");
m_errorReporter.typeError(1406_error, length->location(), "Array with zero length specified.");
else if (lengthType->isFractional())
m_errorReporter.typeError(4323_error, length->location(), "Array with fractional length specified.");
m_errorReporter.typeError(3208_error, length->location(), "Array with fractional length specified.");
else if (lengthType->isNegative())
m_errorReporter.typeError(9308_error, length->location(), "Array with negative length specified.");
m_errorReporter.typeError(3658_error, length->location(), "Array with negative length specified.");
else
lengthValue = lengthType->literalValue(nullptr);
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType, lengthValue);
@@ -345,7 +345,7 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
errorString += " for variable";
}
errorString += ", but " + locationToString(varLoc) + " was given.";
m_errorReporter.typeError(6160_error, _variable.location(), errorString);
m_errorReporter.typeError(6651_error, _variable.location(), errorString);
solAssert(!allowedDataLocations.empty(), "");
varLoc = *allowedDataLocations.begin();
+3 -3
View File
@@ -311,8 +311,8 @@ Token OverrideProxy::functionKind() const
FunctionType const* OverrideProxy::functionType() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const* _item) { return FunctionType(*_item).asCallableFunction(false); },
[&](VariableDeclaration const* _item) { return FunctionType(*_item).asCallableFunction(false); },
[&](FunctionDefinition const* _item) { return FunctionType(*_item).asExternallyCallableFunction(false); },
[&](VariableDeclaration const* _item) { return FunctionType(*_item).asExternallyCallableFunction(false); },
[&](ModifierDefinition const*) -> FunctionType const* { solAssert(false, "Requested function type of modifier."); return nullptr; }
}, m_item);
}
@@ -559,7 +559,7 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
overrideError(
_overriding,
_super,
2837_error,
6959_error,
"Overriding function changes state mutability from \"" +
stateMutabilityToString(_super.stateMutability()) +
"\" to \"" +
+7 -7
View File
@@ -119,7 +119,7 @@ bool ReferencesResolver::visit(Identifier const& _identifier)
else
errorMessage += " Did you mean " + std::move(suggestions) + "?";
}
m_errorReporter.declarationError(8051_error, _identifier.location(), errorMessage);
m_errorReporter.declarationError(7576_error, _identifier.location(), errorMessage);
}
else if (declarations.size() == 1)
_identifier.annotation().referencedDeclaration = declarations.front();
@@ -157,7 +157,7 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
Declaration const* declaration = m_resolver.pathFromCurrentScope(_typeName.namePath());
if (!declaration)
{
m_errorReporter.fatalDeclarationError(7556_error, _typeName.location(), "Identifier not found or not unique.");
m_errorReporter.fatalDeclarationError(7920_error, _typeName.location(), "Identifier not found or not unique.");
return;
}
@@ -210,7 +210,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
if (realName.empty())
{
m_errorReporter.declarationError(
9553_error,
4794_error,
_identifier.location,
"In variable names _slot and _offset can only be used as a suffix."
);
@@ -221,7 +221,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
if (declarations.size() > 1)
{
m_errorReporter.declarationError(
8827_error,
4718_error,
_identifier.location,
"Multiple matching identifiers. Resolving overloaded identifiers is not supported."
);
@@ -233,7 +233,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
if (var->isLocalVariable() && m_yulInsideFunction)
{
m_errorReporter.declarationError(
8477_error,
6578_error,
_identifier.location,
"Cannot access local Solidity variables from inside an inline assembly function."
);
@@ -255,7 +255,7 @@ void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
string namePrefix = identifier.name.str().substr(0, identifier.name.str().find('.'));
if (isSlot || isOffset)
m_errorReporter.declarationError(
8820_error,
9155_error,
identifier.location,
"In variable declarations _slot and _offset can not be used as a suffix."
);
@@ -269,7 +269,7 @@ void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
ssl.append("The shadowed declaration is here:", decl->location());
if (!ssl.infos.empty())
m_errorReporter.declarationError(
6005_error,
3859_error,
identifier.location,
ssl,
namePrefix.size() < identifier.name.str().size() ?
+1
View File
@@ -273,6 +273,7 @@ bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly)
"The msize instruction cannot be used when the Yul optimizer is activated because "
"it can change its semantics. Either disable the Yul optimizer or do not use the instruction."
);
return false;
}
+181 -130
View File
@@ -193,6 +193,18 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c
typeArgument->location(),
"Decoding type " + actualType->toString(false) + " not supported."
);
if (auto referenceType = dynamic_cast<ReferenceType const*>(actualType))
{
auto result = referenceType->validForLocation(referenceType->location());
if (!result)
m_errorReporter.typeError(
6118_error,
typeArgument->location(),
result.message()
);
}
components.push_back(actualType);
}
else
@@ -1511,6 +1523,12 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
{
if (!inlineArrayType)
m_errorReporter.fatalTypeError(6378_error, _tuple.location(), "Unable to deduce common type for array elements.");
else if (!inlineArrayType->nameable())
m_errorReporter.fatalTypeError(
9656_error,
_tuple.location(),
"Unable to deduce nameable type for array elements. Try adding explicit type conversion for the first element."
);
else if (!inlineArrayType->canLiveOutsideStorage())
m_errorReporter.fatalTypeError(1545_error, _tuple.location(), "Type " + inlineArrayType->toString() + " is only valid in storage.");
@@ -1996,19 +2014,16 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
bool const isStructConstructorCall =
_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall;
string msg;
if (isVariadic)
msg +=
auto [errorId, description] = [&]() -> tuple<ErrorId, string> {
string msg = isVariadic ?
"Need at least " +
toString(parameterTypes.size()) +
" arguments for " +
string(isStructConstructorCall ? "struct constructor" : "function call") +
", but provided only " +
toString(arguments.size()) +
".";
else
msg +=
"."
:
"Wrong argument count for " +
string(isStructConstructorCall ? "struct constructor" : "function call") +
": " +
@@ -2018,49 +2033,64 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
toString(parameterTypes.size()) +
".";
// Extend error message in case we try to construct a struct with mapping member.
if (isStructConstructorCall)
{
/// For error message: Struct members that were removed during conversion to memory.
TypePointer const expressionType = type(_functionCall.expression());
TypeType const& t = dynamic_cast<TypeType const&>(*expressionType);
auto const& structType = dynamic_cast<StructType const&>(*t.actualType());
set<string> membersRemovedForStructConstructor = structType.membersMissingInMemory();
if (!membersRemovedForStructConstructor.empty())
// Extend error message in case we try to construct a struct with mapping member.
if (isStructConstructorCall)
{
msg += " Members that have to be skipped in memory:";
for (auto const& member: membersRemovedForStructConstructor)
msg += " " + member;
/// For error message: Struct members that were removed during conversion to memory.
TypePointer const expressionType = type(_functionCall.expression());
auto const& t = dynamic_cast<TypeType const&>(*expressionType);
auto const& structType = dynamic_cast<StructType const&>(*t.actualType());
set<string> membersRemovedForStructConstructor = structType.membersMissingInMemory();
if (!membersRemovedForStructConstructor.empty())
{
msg += " Members that have to be skipped in memory:";
for (auto const& member: membersRemovedForStructConstructor)
msg += " " + member;
}
return { isVariadic ? 1123_error : 9755_error, msg };
}
}
else if (
_functionType->kind() == FunctionType::Kind::BareCall ||
_functionType->kind() == FunctionType::Kind::BareCallCode ||
_functionType->kind() == FunctionType::Kind::BareDelegateCall ||
_functionType->kind() == FunctionType::Kind::BareStaticCall
)
{
if (arguments.empty())
msg +=
else if (
_functionType->kind() == FunctionType::Kind::BareCall ||
_functionType->kind() == FunctionType::Kind::BareCallCode ||
_functionType->kind() == FunctionType::Kind::BareDelegateCall ||
_functionType->kind() == FunctionType::Kind::BareStaticCall
)
{
if (arguments.empty())
return {
isVariadic ? 7653_error : 6138_error,
msg +
" This function requires a single bytes argument."
" Use \"\" as argument to provide empty calldata."
};
else
return {
isVariadic ? 9390_error : 8922_error,
msg +
" This function requires a single bytes argument."
" If all your arguments are value types, you can use"
" abi.encode(...) to properly generate it."
};
}
else if (
_functionType->kind() == FunctionType::Kind::KECCAK256 ||
_functionType->kind() == FunctionType::Kind::SHA256 ||
_functionType->kind() == FunctionType::Kind::RIPEMD160
)
return {
isVariadic ? 1220_error : 4323_error,
msg +
" This function requires a single bytes argument."
" Use \"\" as argument to provide empty calldata.";
" Use abi.encodePacked(...) to obtain the pre-0.5.0"
" behaviour or abi.encode(...) to use ABI encoding."
};
else
msg +=
" This function requires a single bytes argument."
" If all your arguments are value types, you can use"
" abi.encode(...) to properly generate it.";
}
else if (
_functionType->kind() == FunctionType::Kind::KECCAK256 ||
_functionType->kind() == FunctionType::Kind::SHA256 ||
_functionType->kind() == FunctionType::Kind::RIPEMD160
)
msg +=
" This function requires a single bytes argument."
" Use abi.encodePacked(...) to obtain the pre-0.5.0"
" behaviour or abi.encode(...) to use ABI encoding.";
m_errorReporter.typeError(1093_error, _functionCall.location(), msg);
return { isVariadic ? 9308_error : 6160_error, msg };
}();
m_errorReporter.typeError(errorId, _functionCall.location(), description);
return;
}
@@ -2136,33 +2166,43 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
solAssert(!!paramArgMap[i], "unmapped parameter");
if (!type(*paramArgMap[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
{
string msg =
"Invalid type for argument in function call. "
"Invalid implicit conversion from " +
type(*paramArgMap[i])->toString() +
" to " +
parameterTypes[i]->toString() +
" requested.";
if (
_functionType->kind() == FunctionType::Kind::BareCall ||
_functionType->kind() == FunctionType::Kind::BareCallCode ||
_functionType->kind() == FunctionType::Kind::BareDelegateCall ||
_functionType->kind() == FunctionType::Kind::BareStaticCall
)
msg +=
" This function requires a single bytes argument."
" If all your arguments are value types, you can"
" use abi.encode(...) to properly generate it.";
else if (
_functionType->kind() == FunctionType::Kind::KECCAK256 ||
_functionType->kind() == FunctionType::Kind::SHA256 ||
_functionType->kind() == FunctionType::Kind::RIPEMD160
)
msg +=
" This function requires a single bytes argument."
" Use abi.encodePacked(...) to obtain the pre-0.5.0"
" behaviour or abi.encode(...) to use ABI encoding.";
m_errorReporter.typeError(6706_error, paramArgMap[i]->location(), msg);
auto [errorId, description] = [&]() -> tuple<ErrorId, string> {
string msg =
"Invalid type for argument in function call. "
"Invalid implicit conversion from " +
type(*paramArgMap[i])->toString() +
" to " +
parameterTypes[i]->toString() +
" requested.";
if (
_functionType->kind() == FunctionType::Kind::BareCall ||
_functionType->kind() == FunctionType::Kind::BareCallCode ||
_functionType->kind() == FunctionType::Kind::BareDelegateCall ||
_functionType->kind() == FunctionType::Kind::BareStaticCall
)
return {
8051_error,
msg +
" This function requires a single bytes argument."
" If all your arguments are value types, you can"
" use abi.encode(...) to properly generate it."
};
else if (
_functionType->kind() == FunctionType::Kind::KECCAK256 ||
_functionType->kind() == FunctionType::Kind::SHA256 ||
_functionType->kind() == FunctionType::Kind::RIPEMD160
)
return {
7556_error,
msg +
" This function requires a single bytes argument."
" Use abi.encodePacked(...) to obtain the pre-0.5.0"
" behaviour or abi.encode(...) to use ABI encoding."
};
else
return { 9553_error, msg };
}();
m_errorReporter.typeError(errorId, paramArgMap[i]->location(), description);
}
}
}
@@ -2535,7 +2575,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
if (possibleMembers.empty())
{
if (initialMemberCount == 0)
if (initialMemberCount == 0 && !dynamic_cast<ArraySliceType const*>(exprType))
{
// Try to see if the member was removed because it is only available for storage types.
auto storageType = TypeProvider::withLocationIfReference(
@@ -2551,61 +2591,70 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
" outside of storage."
);
}
string errorMsg = "Member \"" + memberName + "\" not found or not visible "
auto [errorId, description] = [&]() -> tuple<ErrorId, string> {
string errorMsg = "Member \"" + memberName + "\" not found or not visible "
"after argument-dependent lookup in " + exprType->toString() + ".";
if (auto const& funType = dynamic_cast<FunctionType const*>(exprType))
{
auto const& t = funType->returnParameterTypes();
if (memberName == "value")
if (auto const* funType = dynamic_cast<FunctionType const*>(exprType))
{
if (funType->kind() == FunctionType::Kind::Creation)
errorMsg = "Constructor for " + t.front()->toString() + " must be payable for member \"value\" to be available.";
else if (
funType->kind() == FunctionType::Kind::DelegateCall ||
funType->kind() == FunctionType::Kind::BareDelegateCall
)
errorMsg = "Member \"value\" is not allowed in delegated calls due to \"msg.value\" persisting.";
else
errorMsg = "Member \"value\" is only available for payable functions.";
}
else if (
t.size() == 1 &&
(t.front()->category() == Type::Category::Struct ||
t.front()->category() == Type::Category::Contract)
)
errorMsg += " Did you intend to call the function?";
}
else if (exprType->category() == Type::Category::Contract)
{
for (auto const& addressMember: TypeProvider::payableAddress()->nativeMembers(nullptr))
if (addressMember.name == memberName)
TypePointers const& t = funType->returnParameterTypes();
if (memberName == "value")
{
Identifier const* var = dynamic_cast<Identifier const*>(&_memberAccess.expression());
string varName = var ? var->name() : "...";
errorMsg += " Use \"address(" + varName + ")." + memberName + "\" to access this address member.";
break;
if (funType->kind() == FunctionType::Kind::Creation)
return {
8827_error,
"Constructor for " + t.front()->toString() + " must be payable for member \"value\" to be available."
};
else if (
funType->kind() == FunctionType::Kind::DelegateCall ||
funType->kind() == FunctionType::Kind::BareDelegateCall
)
return { 8477_error, "Member \"value\" is not allowed in delegated calls due to \"msg.value\" persisting." };
else
return { 8820_error, "Member \"value\" is only available for payable functions." };
}
}
else if (auto addressType = dynamic_cast<AddressType const*>(exprType))
{
// Trigger error when using send or transfer with a non-payable fallback function.
if (memberName == "send" || memberName == "transfer")
{
solAssert(
addressType->stateMutability() != StateMutability::Payable,
"Expected address not-payable as members were not found"
);
errorMsg = "\"send\" and \"transfer\" are only available for objects of type \"address payable\", not \"" + exprType->toString() + "\".";
else if (
t.size() == 1 && (
t.front()->category() == Type::Category::Struct ||
t.front()->category() == Type::Category::Contract
)
)
return { 6005_error, errorMsg + " Did you intend to call the function?" };
}
}
else if (exprType->category() == Type::Category::Contract)
{
for (MemberList::Member const& addressMember: TypeProvider::payableAddress()->nativeMembers(nullptr))
if (addressMember.name == memberName)
{
auto const* var = dynamic_cast<Identifier const*>(&_memberAccess.expression());
string varName = var ? var->name() : "...";
errorMsg += " Use \"address(" + varName + ")." + memberName + "\" to access this address member.";
return { 3125_error, errorMsg };
}
}
else if (auto const* addressType = dynamic_cast<AddressType const*>(exprType))
{
// Trigger error when using send or transfer with a non-payable fallback function.
if (memberName == "send" || memberName == "transfer")
{
solAssert(
addressType->stateMutability() != StateMutability::Payable,
"Expected address not-payable as members were not found"
);
return { 9862_error, "\"send\" and \"transfer\" are only available for objects of type \"address payable\", not \"" + exprType->toString() + "\"." };
}
}
return { 9582_error, errorMsg };
}();
m_errorReporter.fatalTypeError(
4035_error,
errorId,
_memberAccess.location(),
errorMsg
description
);
}
else if (possibleMembers.size() > 1)
@@ -2875,7 +2924,7 @@ bool TypeChecker::visit(IndexRangeAccess const& _access)
if (arrayType->location() != DataLocation::CallData || !arrayType->isDynamicallySized())
m_errorReporter.typeError(1227_error, _access.location(), "Index range access is only supported for dynamic calldata arrays.");
else if (arrayType->baseType()->isDynamicallyEncoded())
m_errorReporter.typeError(1878_error, _access.location(), "Index range access is not supported for arrays with dynamically encoded base types.");
m_errorReporter.typeError(2148_error, _access.location(), "Index range access is not supported for arrays with dynamically encoded base types.");
_access.annotation().type = TypeProvider::arraySlice(*arrayType);
_access.annotation().isLValue = isLValue;
_access.annotation().isPure = isPure;
@@ -3181,17 +3230,17 @@ void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAss
if (_expression.annotation().isLValue)
return;
return m_errorReporter.typeError(1123_error, _expression.location(), [&]() {
auto [errorId, description] = [&]() -> tuple<ErrorId, string> {
if (_expression.annotation().isConstant)
return "Cannot assign to a constant variable.";
return { 6520_error, "Cannot assign to a constant variable." };
if (auto indexAccess = dynamic_cast<IndexAccess const*>(&_expression))
{
if (type(indexAccess->baseExpression())->category() == Type::Category::FixedBytes)
return "Single bytes in fixed bytes arrays cannot be modified.";
return { 4360_error, "Single bytes in fixed bytes arrays cannot be modified." };
else if (auto arrayType = dynamic_cast<ArrayType const*>(type(indexAccess->baseExpression())))
if (arrayType->dataStoredIn(DataLocation::CallData))
return "Calldata arrays are read-only.";
return { 6182_error, "Calldata arrays are read-only." };
}
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_expression))
@@ -3199,18 +3248,20 @@ void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAss
if (auto structType = dynamic_cast<StructType const*>(type(memberAccess->expression())))
{
if (structType->dataStoredIn(DataLocation::CallData))
return "Calldata structs are read-only.";
return { 4156_error, "Calldata structs are read-only." };
}
else if (dynamic_cast<ArrayType const*>(type(memberAccess->expression())))
if (memberAccess->memberName() == "length")
return "Member \"length\" is read-only and cannot be used to resize arrays.";
return { 7567_error, "Member \"length\" is read-only and cannot be used to resize arrays." };
}
if (auto identifier = dynamic_cast<Identifier const*>(&_expression))
if (auto varDecl = dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration))
if (varDecl->isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(identifier->annotation().type))
return "External function arguments of reference type are read-only.";
return { 7128_error, "External function arguments of reference type are read-only." };
return "Expression has to be an lvalue.";
}());
return { 4247_error, "Expression has to be an lvalue." };
}();
m_errorReporter.typeError(errorId, _expression.location(), description);
}
+14 -13
View File
@@ -338,8 +338,14 @@ TypePointer FunctionDefinition::type() const
TypePointer FunctionDefinition::typeViaContractName() const
{
if (annotation().contract->isLibrary())
return FunctionType(*this).asCallableFunction(true);
return TypeProvider::function(*this, FunctionType::Kind::Declaration);
{
if (isPublic())
return FunctionType(*this).asExternallyCallableFunction(true);
else
return TypeProvider::function(*this, FunctionType::Kind::Internal);
}
else
return TypeProvider::function(*this, FunctionType::Kind::Declaration);
}
string FunctionDefinition::externalSignature() const
@@ -369,7 +375,7 @@ FunctionDefinition const& FunctionDefinition::resolveVirtual(
solAssert(!dynamic_cast<ContractDefinition const&>(*scope()).isLibrary(), "");
FunctionType const* functionType = TypeProvider::function(*this)->asCallableFunction(false);
FunctionType const* functionType = TypeProvider::function(*this)->asExternallyCallableFunction(false);
for (ContractDefinition const* c: _mostDerivedContract.annotation().linearizedBaseContracts)
{
@@ -380,7 +386,7 @@ FunctionDefinition const& FunctionDefinition::resolveVirtual(
if (
function->name() == name() &&
!function->isConstructor() &&
FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(*functionType)
FunctionType(*function).asExternallyCallableFunction(false)->hasEqualParameterTypes(*functionType)
)
return *function;
}
@@ -616,18 +622,14 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
if (!hasReferenceOrMappingType() || isStateVariable() || isEventParameter())
return set<Location>{ Location::Unspecified };
else if (isExternalCallableParameter())
{
set<Location> locations{ Location::CallData };
if (isLibraryFunctionParameter())
locations.insert(Location::Storage);
return locations;
}
else if (isCallableOrCatchParameter())
{
set<Location> locations{ Location::Memory };
if (isInternalCallableParameter() || isLibraryFunctionParameter() || isTryCatchParameter())
locations.insert(Location::Storage);
if (!isTryCatchParameter())
locations.insert(Location::CallData);
return locations;
}
else if (isLocalVariable())
@@ -642,8 +644,7 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
case Type::Category::Mapping:
return set<Location>{ Location::Storage };
default:
// TODO: add Location::Calldata once implemented for local variables.
return set<Location>{ Location::Memory, Location::Storage };
return set<Location>{ Location::Memory, Location::Storage, Location::CallData };
}
};
return dataLocations(typeName()->annotation().type, dataLocations);
+20 -6
View File
@@ -349,14 +349,17 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c
MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition const& _scope)
{
// Normalise data location of type.
TypePointer type = TypeProvider::withLocationIfReference(DataLocation::Storage, &_type);
DataLocation typeLocation = DataLocation::Storage;
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
typeLocation = refType->location();
set<Declaration const*> seenFunctions;
MemberList::MemberMap members;
for (ContractDefinition const* contract: _scope.annotation().linearizedBaseContracts)
for (UsingForDirective const* ufd: contract->usingForDirectives())
{
if (ufd->typeName() && *type != *TypeProvider::withLocationIfReference(
DataLocation::Storage,
if (ufd->typeName() && _type != *TypeProvider::withLocationIfReference(
typeLocation,
ufd->typeName()->annotation().type
))
continue;
@@ -370,7 +373,7 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition
seenFunctions.insert(function);
if (function->parameters().empty())
continue;
FunctionTypePointer fun = FunctionType(*function, FunctionType::Kind::External).asCallableFunction(true, true);
FunctionTypePointer fun = FunctionType(*function, FunctionType::Kind::External).asExternallyCallableFunction(true, true);
if (_type.isImplicitlyConvertibleTo(*fun->selfType()))
members.emplace_back(function->name(), fun, function);
}
@@ -2058,7 +2061,7 @@ MemberList::MemberMap ContractType::nativeMembers(ContractDefinition const* _con
for (auto const& it: m_contract.interfaceFunctions())
members.emplace_back(
it.second->declaration().name(),
it.second->asCallableFunction(m_contract.isLibrary()),
it.second->asExternallyCallableFunction(m_contract.isLibrary()),
&it.second->declaration()
);
}
@@ -3026,6 +3029,17 @@ unsigned FunctionType::storageBytes() const
solAssert(false, "Storage size of non-storable function type requested.");
}
bool FunctionType::nameable() const
{
return
(m_kind == Kind::Internal || m_kind == Kind::External) &&
!m_bound &&
!m_arbitraryParameters &&
!m_gasSet &&
!m_valueSet &&
!m_saltSet;
}
vector<tuple<string, TypePointer>> FunctionType::makeStackItems() const
{
vector<tuple<string, TypePointer>> slots;
@@ -3427,7 +3441,7 @@ TypePointer FunctionType::copyAndSetCallOptions(bool _setGas, bool _setValue, bo
);
}
FunctionTypePointer FunctionType::asCallableFunction(bool _inLibrary, bool _bound) const
FunctionTypePointer FunctionType::asExternallyCallableFunction(bool _inLibrary, bool _bound) const
{
if (_bound)
solAssert(!m_parameterTypes.empty(), "");
+17 -2
View File
@@ -263,6 +263,10 @@ public:
/// Returns true if the type can be stored as a value (as opposed to a reference) on the stack,
/// i.e. it behaves differently in lvalue context and in value context.
virtual bool isValueType() const { return false; }
/// @returns true if this type can be used for variables. It returns false for
/// types like magic types, literals and function types with a kind that is not
/// internal or external.
virtual bool nameable() const { return false; }
/// @returns a list of named and typed stack items that determine the layout of this type on the stack.
/// A stack item either has an empty name and type ``nullptr`` referring to a single stack slot, or
/// has a non-empty name and a valid type referring to the stack layout of that type.
@@ -402,6 +406,7 @@ public:
unsigned storageBytes() const override { return 160 / 8; }
bool leftAligned() const override { return false; }
bool isValueType() const override { return true; }
bool nameable() const override { return true; }
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
@@ -446,6 +451,7 @@ public:
unsigned storageBytes() const override { return m_bits / 8; }
bool leftAligned() const override { return false; }
bool isValueType() const override { return true; }
bool nameable() const override { return true; }
std::string toString(bool _short) const override;
@@ -492,6 +498,7 @@ public:
unsigned storageBytes() const override { return m_totalBits / 8; }
bool leftAligned() const override { return false; }
bool isValueType() const override { return true; }
bool nameable() const override { return true; }
std::string toString(bool _short) const override;
@@ -639,6 +646,7 @@ public:
unsigned storageBytes() const override { return m_bytes; }
bool leftAligned() const override { return true; }
bool isValueType() const override { return true; }
bool nameable() const override { return true; }
std::string toString(bool) const override { return "bytes" + util::toString(m_bytes); }
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
@@ -666,6 +674,7 @@ public:
unsigned storageBytes() const override { return 1; }
bool leftAligned() const override { return false; }
bool isValueType() const override { return true; }
bool nameable() const override { return true; }
std::string toString(bool) const override { return "bool"; }
u256 literalValue(Literal const* _literal) const override;
@@ -773,6 +782,7 @@ public:
bool isDynamicallyEncoded() const override;
u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); }
bool nameable() const override { return true; }
std::string toString(bool _short) const override;
std::string canonicalName() const override;
std::string signatureInExternalFunction(bool _structsByName) const override;
@@ -875,6 +885,7 @@ public:
bool leftAligned() const override { solAssert(!isSuper(), ""); return false; }
bool canLiveOutsideStorage() const override { return !isSuper(); }
bool isValueType() const override { return !isSuper(); }
bool nameable() const override { return !isSuper(); }
std::string toString(bool _short) const override;
std::string canonicalName() const override;
@@ -935,6 +946,7 @@ public:
u256 memoryDataSize() const override;
u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return true; }
bool nameable() const override { return true; }
std::string toString(bool _short) const override;
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
@@ -997,6 +1009,7 @@ public:
std::string toString(bool _short) const override;
std::string canonicalName() const override;
bool isValueType() const override { return true; }
bool nameable() const override { return true; }
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
TypePointer encodingType() const override;
@@ -1202,6 +1215,7 @@ public:
bool leftAligned() const override;
unsigned storageBytes() const override;
bool isValueType() const override { return true; }
bool nameable() const override;
bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
bool hasSimpleZeroValueInMemory() const override { return false; }
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
@@ -1290,11 +1304,11 @@ public:
/// @returns a copy of this function type where the location of reference types is changed
/// from CallData to Memory. This is the type that would be used when the function is
/// called, as opposed to the parameter types that are available inside the function body.
/// called externally, as opposed to the parameter types that are available inside the function body.
/// Also supports variants to be used for library or bound calls.
/// @param _inLibrary if true, uses DelegateCall as location.
/// @param _bound if true, the function type is set to be bound.
FunctionTypePointer asCallableFunction(bool _inLibrary, bool _bound = false) const;
FunctionTypePointer asExternallyCallableFunction(bool _inLibrary, bool _bound = false) const;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
@@ -1339,6 +1353,7 @@ public:
bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; }
/// Cannot be stored in memory, but just in case.
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
bool nameable() const override { return true; }
Type const* keyType() const { return m_keyType; }
Type const* valueType() const { return m_valueType; }
+13
View File
@@ -1022,6 +1022,10 @@ void CompilerUtils::convertType(
case Type::Category::ArraySlice:
{
auto& typeOnStack = dynamic_cast<ArraySliceType const&>(_typeOnStack);
solUnimplementedAssert(
_targetType.dataStoredIn(DataLocation::CallData),
"Conversion from calldata slices to memory not yet implemented."
);
solAssert(_targetType == typeOnStack.arrayType(), "");
solUnimplementedAssert(
typeOnStack.arrayType().location() == DataLocation::CallData &&
@@ -1218,6 +1222,15 @@ void CompilerUtils::pushZeroValue(Type const& _type)
m_context << u256(0);
return;
}
if (referenceType->location() == DataLocation::CallData)
{
solAssert(referenceType->sizeOnStack() == 1 || referenceType->sizeOnStack() == 2, "");
m_context << Instruction::CALLDATASIZE;
if (referenceType->sizeOnStack() == 2)
m_context << 0;
return;
}
solAssert(referenceType->location() == DataLocation::Memory, "");
if (auto arrayType = dynamic_cast<ArrayType const*>(&_type))
if (arrayType->isDynamicallySized())
+25 -3
View File
@@ -1667,8 +1667,10 @@ string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType co
for (size_t i = 0; i < members.size(); ++i)
{
solAssert(members[i]->memoryHeadSize() == 32, "");
solAssert(members[i]->dataStoredIn(DataLocation::Memory), "");
memberParams[i]["zeroValue"] = zeroValueFunction(*members[i], false);
memberParams[i]["zeroValue"] = zeroValueFunction(
*TypeProvider::withLocationIfReference(DataLocation::Memory, members[i]),
false
);
}
templ("member", memberParams);
return templ.render();
@@ -2238,6 +2240,27 @@ string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFunctio
("functionName", functionName)
.render();
if (_type.dataStoredIn(DataLocation::CallData))
{
solAssert(
_type.category() == Type::Category::Struct ||
_type.category() == Type::Category::Array,
"");
Whiskers templ(R"(
function <functionName>() -> offset<?hasLength>, length</hasLength> {
offset := calldatasize()
<?hasLength> length := 0 </hasLength>
}
)");
templ("functionName", functionName);
templ("hasLength",
_type.category() == Type::Category::Array &&
dynamic_cast<ArrayType const&>(_type).isDynamicallySized()
);
return templ.render();
}
Whiskers templ(R"(
function <functionName>() -> ret {
ret := <zeroValue>
@@ -2622,4 +2645,3 @@ string YulUtilFunctions::copyConstructorArgumentsToMemoryFunction(
.render();
});
}
+47 -60
View File
@@ -29,6 +29,8 @@
#include <libsolutil/Whiskers.h>
#include <libsolutil/StringUtils.h>
#include <boost/range/adaptor/map.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::util;
@@ -121,52 +123,55 @@ string IRGenerationContext::newYulVariable()
return "_" + to_string(++m_varCounter);
}
string IRGenerationContext::generateInternalDispatchFunction(YulArity const& _arity)
void IRGenerationContext::initializeInternalDispatch(InternalDispatchMap _internalDispatch)
{
string funName = IRNames::internalDispatch(_arity);
return m_functions.createFunction(funName, [&]() {
Whiskers templ(R"(
function <functionName>(fun <comma> <in>) <arrow> <out> {
switch fun
<#cases>
case <funID>
{
<out> <assignment_op> <name>(<in>)
}
</cases>
default { invalid() }
}
)");
templ("functionName", funName);
templ("comma", _arity.in > 0 ? "," : "");
templ("in", suffixedVariableNameList("in_", 0, _arity.in));
templ("arrow", _arity.out > 0 ? "->" : "");
templ("assignment_op", _arity.out > 0 ? ":=" : "");
templ("out", suffixedVariableNameList("out_", 0, _arity.out));
vector<map<string, string>> cases;
for (FunctionDefinition const* function: collectFunctionsOfArity(_arity))
{
solAssert(function, "");
solAssert(
YulArity::fromType(*TypeProvider::function(*function, FunctionType::Kind::Internal)) == _arity,
"A single dispatch function can only handle functions of one arity"
);
solAssert(!function->isConstructor(), "");
// 0 is reserved for uninitialized function pointers
solAssert(function->id() != 0, "Unexpected function ID: 0");
cases.emplace_back(map<string, string>{
{"funID", to_string(function->id())},
{"name", IRNames::function(*function)}
});
solAssert(internalDispatchClean(), "");
for (set<FunctionDefinition const*> const& functions: _internalDispatch | boost::adaptors::map_values)
for (auto function: functions)
enqueueFunctionForCodeGeneration(*function);
}
templ("cases", move(cases));
return templ.render();
});
m_internalDispatchMap = move(_internalDispatch);
}
InternalDispatchMap IRGenerationContext::consumeInternalDispatchMap()
{
m_directInternalFunctionCalls.clear();
InternalDispatchMap internalDispatch = move(m_internalDispatchMap);
m_internalDispatchMap.clear();
return internalDispatch;
}
void IRGenerationContext::internalFunctionCalledDirectly(Expression const& _expression)
{
solAssert(m_directInternalFunctionCalls.count(&_expression) == 0, "");
m_directInternalFunctionCalls.insert(&_expression);
}
void IRGenerationContext::internalFunctionAccessed(Expression const& _expression, FunctionDefinition const& _function)
{
solAssert(
IRHelpers::referencedFunctionDeclaration(_expression) &&
_function.resolveVirtual(mostDerivedContract()) ==
IRHelpers::referencedFunctionDeclaration(_expression)->resolveVirtual(mostDerivedContract()),
"Function definition does not match the expression"
);
if (m_directInternalFunctionCalls.count(&_expression) == 0)
{
FunctionType const* functionType = TypeProvider::function(_function, FunctionType::Kind::Internal);
solAssert(functionType, "");
m_internalDispatchMap[YulArity::fromType(*functionType)].insert(&_function);
enqueueFunctionForCodeGeneration(_function);
}
}
void IRGenerationContext::internalFunctionCalledThroughDispatch(YulArity const& _arity)
{
m_internalDispatchMap.try_emplace(_arity);
}
YulUtilFunctions IRGenerationContext::utils()
@@ -183,21 +188,3 @@ std::string IRGenerationContext::revertReasonIfDebug(std::string const& _message
{
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
}
set<FunctionDefinition const*> IRGenerationContext::collectFunctionsOfArity(YulArity const& _arity)
{
// UNIMPLEMENTED: Internal library calls via pointers are not implemented yet.
// We're not returning any internal library functions here even though it's possible
// to call them via pointers. Right now such calls end will up triggering the `default` case in
// the switch in the generated dispatch function.
set<FunctionDefinition const*> functions;
for (auto const& contract: mostDerivedContract().annotation().linearizedBaseContracts)
for (FunctionDefinition const* function: contract->definedFunctions())
if (
!function->isConstructor() &&
YulArity::fromType(*TypeProvider::function(*function, FunctionType::Kind::Internal)) == _arity
)
functions.insert(function);
return functions;
}
+29 -3
View File
@@ -43,6 +43,8 @@ namespace solidity::frontend
class YulUtilFunctions;
class ABIFunctions;
using InternalDispatchMap = std::map<YulArity, std::set<FunctionDefinition const*>>;
/**
* Class that contains contextual information during IR generation.
*/
@@ -102,7 +104,26 @@ public:
std::string newYulVariable();
std::string generateInternalDispatchFunction(YulArity const& _arity);
void initializeInternalDispatch(InternalDispatchMap _internalDispatchMap);
InternalDispatchMap consumeInternalDispatchMap();
bool internalDispatchClean() const { return m_internalDispatchMap.empty() && m_directInternalFunctionCalls.empty(); }
/// Notifies the context that a function call that needs to go through internal dispatch was
/// encountered while visiting the AST. This ensures that the corresponding dispatch function
/// gets added to the dispatch map even if there are no entries in it (which may happen if
/// the code contains a call to an uninitialized function variable).
void internalFunctionCalledThroughDispatch(YulArity const& _arity);
/// Notifies the context that a direct function call (i.e. not through internal dispatch) was
/// encountered while visiting the AST. This lets the context know that the function should
/// not be added to the dispatch (unless there are also indirect calls to it elsewhere else).
void internalFunctionCalledDirectly(Expression const& _expression);
/// Notifies the context that a name representing an internal function has been found while
/// visiting the AST. If the name has not been reported as a direct call using
/// @a internalFunctionCalledDirectly(), it's assumed to represent function variable access
/// and the function gets added to internal dispatch.
void internalFunctionAccessed(Expression const& _expression, FunctionDefinition const& _function);
/// @returns a new copy of the utility function generator (but using the same function set).
YulUtilFunctions utils();
@@ -120,8 +141,6 @@ public:
std::set<ContractDefinition const*, ASTNode::CompareByID>& subObjectsCreated() { return m_subObjects; }
private:
std::set<FunctionDefinition const*> collectFunctionsOfArity(YulArity const& _arity);
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
OptimiserSettings m_optimiserSettings;
@@ -147,6 +166,13 @@ private:
/// all platforms - which is a property guaranteed by MultiUseYulFunctionCollector.
std::set<FunctionDefinition const*> m_functionGenerationQueue;
/// Collection of functions that need to be callable via internal dispatch.
/// Note that having a key with an empty set of functions is a valid situation. It means that
/// the code contains a call via a pointer even though a specific function is never assigned to it.
/// It will fail at runtime but the code must still compile.
InternalDispatchMap m_internalDispatchMap;
std::set<Expression const*> m_directInternalFunctionCalls;
std::set<ContractDefinition const*, ASTNode::CompareByID> m_subObjects;
};
+76
View File
@@ -38,6 +38,8 @@
#include <liblangutil/SourceReferenceFormatter.h>
#include <boost/range/adaptor/map.hpp>
#include <sstream>
using namespace std;
@@ -137,14 +139,22 @@ string IRGenerator::generate(
t("deploy", deployCode(_contract));
generateImplicitConstructors(_contract);
generateQueuedFunctions();
InternalDispatchMap internalDispatchMap = generateInternalDispatchFunctions();
t("functions", m_context.functionCollector().requestedFunctions());
t("subObjects", subObjectSources(m_context.subObjectsCreated()));
resetContext(_contract);
// NOTE: Function pointers can be passed from creation code via storage variables. We need to
// get all the functions they could point to into the dispatch functions even if they're never
// referenced by name in the runtime code.
m_context.initializeInternalDispatch(move(internalDispatchMap));
// Do not register immutables to avoid assignment.
t("RuntimeObject", IRNames::runtimeObject(_contract));
t("dispatch", dispatchRoutine(_contract));
generateQueuedFunctions();
generateInternalDispatchFunctions();
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
t("runtimeSubObjects", subObjectSources(m_context.subObjectsCreated()));
return t.render();
@@ -164,6 +174,68 @@ void IRGenerator::generateQueuedFunctions()
generateFunction(*m_context.dequeueFunctionForCodeGeneration());
}
InternalDispatchMap IRGenerator::generateInternalDispatchFunctions()
{
solAssert(
m_context.functionGenerationQueueEmpty(),
"At this point all the enqueued functions should have been generated. "
"Otherwise the dispatch may be incomplete."
);
InternalDispatchMap internalDispatchMap = m_context.consumeInternalDispatchMap();
for (YulArity const& arity: internalDispatchMap | boost::adaptors::map_keys)
{
string funName = IRNames::internalDispatch(arity);
m_context.functionCollector().createFunction(funName, [&]() {
Whiskers templ(R"(
function <functionName>(fun<?+in>, <in></+in>) <?+out>-> <out></+out> {
switch fun
<#cases>
case <funID>
{
<?+out> <out> :=</+out> <name>(<in>)
}
</cases>
default { invalid() }
}
)");
templ("functionName", funName);
templ("in", suffixedVariableNameList("in_", 0, arity.in));
templ("out", suffixedVariableNameList("out_", 0, arity.out));
vector<map<string, string>> cases;
for (FunctionDefinition const* function: internalDispatchMap.at(arity))
{
solAssert(function, "");
solAssert(
YulArity::fromType(*TypeProvider::function(*function, FunctionType::Kind::Internal)) == arity,
"A single dispatch function can only handle functions of one arity"
);
solAssert(!function->isConstructor(), "");
// 0 is reserved for uninitialized function pointers
solAssert(function->id() != 0, "Unexpected function ID: 0");
solAssert(m_context.functionCollector().contains(IRNames::function(*function)), "");
cases.emplace_back(map<string, string>{
{"funID", to_string(function->id())},
{"name", IRNames::function(*function)}
});
}
templ("cases", move(cases));
return templ.render();
});
}
solAssert(m_context.internalDispatchClean(), "");
solAssert(
m_context.functionGenerationQueueEmpty(),
"Internal dispatch generation must not add new functions to generation queue because they won't be proeessed."
);
return internalDispatchMap;
}
string IRGenerator::generateFunction(FunctionDefinition const& _function)
{
string functionName = IRNames::function(_function);
@@ -556,6 +628,10 @@ void IRGenerator::resetContext(ContractDefinition const& _contract)
m_context.functionCollector().requestedFunctions().empty(),
"Reset context while it still had functions."
);
solAssert(
m_context.internalDispatchClean(),
"Reset internal dispatch map without consuming it."
);
m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings);
m_context.setMostDerivedContract(_contract);
+5
View File
@@ -65,6 +65,11 @@ private:
/// Generates code for all the functions from the function generation queue.
/// The resulting code is stored in the function collector in IRGenerationContext.
void generateQueuedFunctions();
/// Generates all the internal dispatch functions necessary to handle any function that could
/// possibly be called via a pointer.
/// @return The content of the dispatch for reuse in runtime code. Reuse is necessary because
/// pointers to functions can be passed from the creation code in storage variables.
InternalDispatchMap generateInternalDispatchFunctions();
/// Generates code for and returns the name of the function.
std::string generateFunction(FunctionDefinition const& _function);
/// Generates a getter for the given declaration and returns its name
@@ -583,6 +583,20 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
return false;
}
bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
{
FunctionTypePointer functionType = dynamic_cast<FunctionType const*>(&type(_functionCall.expression()));
if (
functionType &&
functionType->kind() == FunctionType::Kind::Internal &&
!functionType->bound() &&
IRHelpers::referencedFunctionDeclaration(_functionCall.expression())
)
m_context.internalFunctionCalledDirectly(_functionCall.expression());
return true;
}
void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
{
solUnimplementedAssert(
@@ -661,8 +675,6 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
solAssert(functionDef->isImplemented(), "");
}
else
solAssert(!functionType->hasDeclaration(), "");
solAssert(!functionType->takesArbitraryParameters(), "");
@@ -688,9 +700,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
else
{
YulArity arity = YulArity::fromType(*functionType);
m_context.internalFunctionCalledThroughDispatch(arity);
define(_functionCall) <<
// NOTE: generateInternalDispatchFunction() takes care of adding the function to function generation queue
m_context.generateInternalDispatchFunction(arity) <<
IRNames::internalDispatch(arity) <<
"(" <<
IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
joinHumanReadablePrefixed(args) <<
@@ -869,6 +882,45 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
m_code << templ.render();
break;
}
case FunctionType::Kind::ABIDecode:
{
Whiskers templ(R"(
<?+retVars>let <retVars> := </+retVars> <abiDecode>(<offset>, add(<offset>, <length>))
)");
TypePointer firstArgType = arguments.front()->annotation().type;
TypePointers targetTypes;
if (TupleType const* targetTupleType = dynamic_cast<TupleType const*>(_functionCall.annotation().type))
targetTypes = targetTupleType->components();
else
targetTypes = TypePointers{_functionCall.annotation().type};
if (
auto referenceType = dynamic_cast<ReferenceType const*>(firstArgType);
referenceType && referenceType->dataStoredIn(DataLocation::CallData)
)
{
solAssert(referenceType->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata()), "");
IRVariable var = convert(*arguments[0], *TypeProvider::bytesCalldata());
templ("abiDecode", m_context.abiFunctions().tupleDecoder(targetTypes, false));
templ("offset", var.part("offset").name());
templ("length", var.part("length").name());
}
else
{
IRVariable var = convert(*arguments[0], *TypeProvider::bytesMemory());
templ("abiDecode", m_context.abiFunctions().tupleDecoder(targetTypes, true));
templ("offset", "add(" + var.part("mpos").name() + ", 32)");
templ("length",
m_utils.arrayLengthFunction(*TypeProvider::bytesMemory()) + "(" + var.part("mpos").name() + ")"
);
}
templ("retVars", IRVariable(_functionCall).commaSeparatedList());
m_code << templ.render();
break;
}
case FunctionType::Kind::Revert:
{
solAssert(arguments.size() == parameterTypes.size(), "");
@@ -1354,7 +1406,6 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
else if (member == "data")
{
IRVariable var(_memberAccess);
declare(var);
define(var.part("offset")) << "0\n";
define(var.part("length")) << "calldatasize()\n";
}
@@ -1492,7 +1543,10 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
break;
case FunctionType::Kind::Internal:
if (auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration))
{
define(_memberAccess) << to_string(function->id()) << "\n";
m_context.internalFunctionAccessed(_memberAccess, *function);
}
else
solAssert(false, "Function not found in member access");
break;
@@ -1756,7 +1810,14 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
return;
}
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
define(_identifier) << to_string(functionDef->resolveVirtual(m_context.mostDerivedContract()).id()) << "\n";
{
FunctionDefinition const& resolvedFunctionDef = functionDef->resolveVirtual(m_context.mostDerivedContract());
define(_identifier) << to_string(resolvedFunctionDef.id()) << "\n";
solAssert(resolvedFunctionDef.functionType(true), "");
solAssert(resolvedFunctionDef.functionType(true)->kind() == FunctionType::Kind::Internal, "");
m_context.internalFunctionAccessed(_identifier, resolvedFunctionDef);
}
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
handleVariableReference(*varDecl, _identifier);
else if (dynamic_cast<ContractDefinition const*>(declaration))
@@ -70,6 +70,7 @@ public:
void endVisit(Return const& _return) override;
void endVisit(UnaryOperation const& _unaryOperation) override;
bool visit(BinaryOperation const& _binOp) override;
bool visit(FunctionCall const& _funCall) override;
void endVisit(FunctionCall const& _funCall) override;
void endVisit(FunctionCallOptions const& _funCallOptions) override;
void endVisit(MemberAccess const& _memberAccess) override;
+2 -2
View File
@@ -74,8 +74,8 @@ bool SMTEncoder::visit(ContractDefinition const& _contract)
if (
function->name() == baseFunction->name() &&
function->kind() == baseFunction->kind() &&
FunctionType(*function).asCallableFunction(false)->
hasEqualParameterTypes(*FunctionType(*baseFunction).asCallableFunction(false))
FunctionType(*function).asExternallyCallableFunction(false)->
hasEqualParameterTypes(*FunctionType(*baseFunction).asExternallyCallableFunction(false))
)
{
overridden = true;