mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #10208 from ethereum/offsetLengthCalldata
Support .offset and .length for dynamic calldata arrays
This commit is contained in:
commit
9f0a631948
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
Language Features:
|
Language Features:
|
||||||
* Ability to select the abi coder using ``pragma abicoder v1`` and ``pragma abicoder v2``.
|
* Ability to select the abi coder using ``pragma abicoder v1`` and ``pragma abicoder v2``.
|
||||||
|
* Inline Assembly: Use ``.offset`` and ``.length`` for calldata variables of dynamic array type to access their calldata offset and length (number of elements). Both of them can also be assigned to.
|
||||||
* Immutable variables with literal number values are considered pure.
|
* Immutable variables with literal number values are considered pure.
|
||||||
|
|
||||||
Compiler Features:
|
Compiler Features:
|
||||||
@ -34,6 +35,9 @@ Bugfixes:
|
|||||||
* NatSpec: Fix internal error when inheriting return parameter documentation but the parameter names differ between base and inherited.
|
* NatSpec: Fix internal error when inheriting return parameter documentation but the parameter names differ between base and inherited.
|
||||||
* Standard JSON: Fix library addresses specified in ``libraries`` being used for linking even if the file names do not match.
|
* Standard JSON: Fix library addresses specified in ``libraries`` being used for linking even if the file names do not match.
|
||||||
|
|
||||||
|
AST Changes:
|
||||||
|
* New member ``suffix`` for inline assembly identifiers. Currently supported values are ``"slot"``, ``"offset"`` and ``"length"`` to access the components of a Solidity variable.
|
||||||
|
|
||||||
|
|
||||||
### 0.7.4 (2020-10-19)
|
### 0.7.4 (2020-10-19)
|
||||||
|
|
||||||
|
@ -135,6 +135,10 @@ inside that slot. To retrieve the slot pointed to by the variable ``x``, you
|
|||||||
use ``x.slot``, and to retrieve the byte-offset you use ``x.offset``.
|
use ``x.slot``, and to retrieve the byte-offset you use ``x.offset``.
|
||||||
Using ``x`` itself will result in an error.
|
Using ``x`` itself will result in an error.
|
||||||
|
|
||||||
|
For dynamic calldata arrays, you can access
|
||||||
|
their calldata offset (in bytes) and length (number of elements) using ``x.offset`` and ``x.length``.
|
||||||
|
Both expressions can also be assigned to.
|
||||||
|
|
||||||
Local Solidity variables are available for assignments, for example:
|
Local Solidity variables are available for assignments, for example:
|
||||||
|
|
||||||
.. code::
|
.. code::
|
||||||
|
@ -215,22 +215,21 @@ void ReferencesResolver::operator()(yul::FunctionDefinition const& _function)
|
|||||||
|
|
||||||
void ReferencesResolver::operator()(yul::Identifier const& _identifier)
|
void ReferencesResolver::operator()(yul::Identifier const& _identifier)
|
||||||
{
|
{
|
||||||
bool isSlot = boost::algorithm::ends_with(_identifier.name.str(), ".slot");
|
static set<string> suffixes{"slot", "offset", "length"};
|
||||||
bool isOffset = boost::algorithm::ends_with(_identifier.name.str(), ".offset");
|
string suffix;
|
||||||
|
for (string const& s: suffixes)
|
||||||
|
if (boost::algorithm::ends_with(_identifier.name.str(), "." + s))
|
||||||
|
suffix = s;
|
||||||
|
|
||||||
// Could also use `pathFromCurrentScope`, split by '.'
|
// Could also use `pathFromCurrentScope`, split by '.'
|
||||||
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str());
|
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str());
|
||||||
if (isSlot || isOffset)
|
if (!suffix.empty())
|
||||||
{
|
{
|
||||||
// special mode to access storage variables
|
// special mode to access storage variables
|
||||||
if (!declarations.empty())
|
if (!declarations.empty())
|
||||||
// the special identifier exists itself, we should not allow that.
|
// the special identifier exists itself, we should not allow that.
|
||||||
return;
|
return;
|
||||||
string realName = _identifier.name.str().substr(0, _identifier.name.str().size() - (
|
string realName = _identifier.name.str().substr(0, _identifier.name.str().size() - suffix.size() - 1);
|
||||||
isSlot ?
|
|
||||||
string(".slot").size() :
|
|
||||||
string(".offset").size()
|
|
||||||
));
|
|
||||||
solAssert(!realName.empty(), "Empty name.");
|
solAssert(!realName.empty(), "Empty name.");
|
||||||
declarations = m_resolver.nameFromCurrentScope(realName);
|
declarations = m_resolver.nameFromCurrentScope(realName);
|
||||||
if (!declarations.empty())
|
if (!declarations.empty())
|
||||||
@ -255,7 +254,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
|
|||||||
m_errorReporter.declarationError(
|
m_errorReporter.declarationError(
|
||||||
9467_error,
|
9467_error,
|
||||||
_identifier.location,
|
_identifier.location,
|
||||||
"Identifier not found. Use ``.slot`` and ``.offset`` to access storage variables."
|
"Identifier not found. Use \".slot\" and \".offset\" to access storage variables."
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -270,8 +269,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_yulAnnotation->externalReferences[&_identifier].isSlot = isSlot;
|
m_yulAnnotation->externalReferences[&_identifier].suffix = move(suffix);
|
||||||
m_yulAnnotation->externalReferences[&_identifier].isOffset = isOffset;
|
|
||||||
m_yulAnnotation->externalReferences[&_identifier].declaration = declarations.front();
|
m_yulAnnotation->externalReferences[&_identifier].declaration = declarations.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -740,7 +740,6 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
InlineAssemblyAnnotation::ExternalIdentifierInfo& identifierInfo = ref->second;
|
InlineAssemblyAnnotation::ExternalIdentifierInfo& identifierInfo = ref->second;
|
||||||
Declaration const* declaration = identifierInfo.declaration;
|
Declaration const* declaration = identifierInfo.declaration;
|
||||||
solAssert(!!declaration, "");
|
solAssert(!!declaration, "");
|
||||||
bool requiresStorage = identifierInfo.isSlot || identifierInfo.isOffset;
|
|
||||||
if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
|
if (auto var = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||||
{
|
{
|
||||||
solAssert(var->type(), "Expected variable type!");
|
solAssert(var->type(), "Expected variable type!");
|
||||||
@ -763,7 +762,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
m_errorReporter.typeError(6252_error, _identifier.location, "Constant variables cannot be assigned to.");
|
m_errorReporter.typeError(6252_error, _identifier.location, "Constant variables cannot be assigned to.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (requiresStorage)
|
else if (!identifierInfo.suffix.empty())
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(6617_error, _identifier.location, "The suffixes .offset and .slot can only be used on non-constant storage variables.");
|
m_errorReporter.typeError(6617_error, _identifier.location, "The suffixes .offset and .slot can only be used on non-constant storage variables.");
|
||||||
return false;
|
return false;
|
||||||
@ -789,51 +788,80 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
|
|
||||||
solAssert(!dynamic_cast<FixedPointType const*>(var->type()), "FixedPointType not implemented.");
|
solAssert(!dynamic_cast<FixedPointType const*>(var->type()), "FixedPointType not implemented.");
|
||||||
|
|
||||||
if (requiresStorage)
|
if (!identifierInfo.suffix.empty())
|
||||||
{
|
{
|
||||||
if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage))
|
string const& suffix = identifierInfo.suffix;
|
||||||
|
solAssert((set<string>{"offset", "slot", "length"}).count(suffix), "");
|
||||||
|
if (var->isStateVariable() || var->type()->dataStoredIn(DataLocation::Storage))
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(3622_error, _identifier.location, "The suffixes .offset and .slot can only be used on storage variables.");
|
if (suffix != "slot" && suffix != "offset")
|
||||||
return false;
|
{
|
||||||
|
m_errorReporter.typeError(4656_error, _identifier.location, "State variables only support \".slot\" and \".offset\".");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (_context == yul::IdentifierContext::LValue)
|
||||||
|
{
|
||||||
|
if (var->isStateVariable())
|
||||||
|
{
|
||||||
|
m_errorReporter.typeError(4713_error, _identifier.location, "State variables cannot be assigned to - you have to use \"sstore()\".");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (suffix != "slot")
|
||||||
|
{
|
||||||
|
m_errorReporter.typeError(9739_error, _identifier.location, "Only .slot can be assigned to.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (_context == yul::IdentifierContext::LValue)
|
else if (
|
||||||
|
auto const* arrayType = dynamic_cast<ArrayType const*>(var->type());
|
||||||
|
arrayType && arrayType->isDynamicallySized() && arrayType->dataStoredIn(DataLocation::CallData)
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (var->isStateVariable())
|
if (suffix != "offset" && suffix != "length")
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(4713_error, _identifier.location, "State variables cannot be assigned to - you have to use \"sstore()\".");
|
m_errorReporter.typeError(1536_error, _identifier.location, "Calldata variables only support \".offset\" and \".length\".");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (identifierInfo.isOffset)
|
}
|
||||||
{
|
else
|
||||||
m_errorReporter.typeError(9739_error, _identifier.location, "Only .slot can be assigned to.");
|
{
|
||||||
return false;
|
m_errorReporter.typeError(3622_error, _identifier.location, "The suffix \"." + suffix + "\" is not supported by this variable or type.");
|
||||||
}
|
return false;
|
||||||
else
|
|
||||||
solAssert(identifierInfo.isSlot, "");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!var->isConstant() && var->isStateVariable())
|
else if (!var->isConstant() && var->isStateVariable())
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(1408_error, _identifier.location, "Only local variables are supported. To access storage variables, use the .slot and .offset suffixes.");
|
m_errorReporter.typeError(
|
||||||
|
1408_error,
|
||||||
|
_identifier.location,
|
||||||
|
"Only local variables are supported. To access storage variables, use the \".slot\" and \".offset\" suffixes."
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (var->type()->dataStoredIn(DataLocation::Storage))
|
else if (var->type()->dataStoredIn(DataLocation::Storage))
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(9068_error, _identifier.location, "You have to use the .slot or .offset suffix to access storage reference variables.");
|
m_errorReporter.typeError(9068_error, _identifier.location, "You have to use the \".slot\" or \".offset\" suffix to access storage reference variables.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (var->type()->sizeOnStack() != 1)
|
else if (var->type()->sizeOnStack() != 1)
|
||||||
{
|
{
|
||||||
if (var->type()->dataStoredIn(DataLocation::CallData))
|
if (
|
||||||
m_errorReporter.typeError(2370_error, _identifier.location, "Call data elements cannot be accessed directly. Copy to a local variable first or use \"calldataload\" or \"calldatacopy\" with manually determined offsets and sizes.");
|
auto const* arrayType = dynamic_cast<ArrayType const*>(var->type());
|
||||||
|
arrayType && arrayType->isDynamicallySized() && arrayType->dataStoredIn(DataLocation::CallData)
|
||||||
|
)
|
||||||
|
m_errorReporter.typeError(1397_error, _identifier.location, "Call data elements cannot be accessed directly. Use \".offset\" and \".length\" to access the calldata offset and length of this array and then use \"calldatacopy\".");
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
solAssert(!var->type()->dataStoredIn(DataLocation::CallData), "");
|
||||||
m_errorReporter.typeError(9857_error, _identifier.location, "Only types that use one stack slot are supported.");
|
m_errorReporter.typeError(9857_error, _identifier.location, "Only types that use one stack slot are supported.");
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (requiresStorage)
|
else if (!identifierInfo.suffix.empty())
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(7944_error, _identifier.location, "The suffixes .offset and .slot can only be used on storage variables.");
|
m_errorReporter.typeError(7944_error, _identifier.location, "The suffixes \".offset\", \".slot\" and \".length\" can only be used with variables.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (_context == yul::IdentifierContext::LValue)
|
else if (_context == yul::IdentifierContext::LValue)
|
||||||
|
@ -198,8 +198,8 @@ struct InlineAssemblyAnnotation: StatementAnnotation
|
|||||||
struct ExternalIdentifierInfo
|
struct ExternalIdentifierInfo
|
||||||
{
|
{
|
||||||
Declaration const* declaration = nullptr;
|
Declaration const* declaration = nullptr;
|
||||||
bool isSlot = false; ///< Whether the storage slot of a variable is queried.
|
/// Suffix used, one of "slot", "offset", "length" or empty.
|
||||||
bool isOffset = false; ///< Whether the intra-slot offset of a storage variable is queried.
|
std::string suffix;
|
||||||
size_t valueSize = size_t(-1);
|
size_t valueSize = size_t(-1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -224,8 +224,10 @@ Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<yul::Identifie
|
|||||||
Json::Value tuple(Json::objectValue);
|
Json::Value tuple(Json::objectValue);
|
||||||
tuple["src"] = sourceLocationToString(_info.first->location);
|
tuple["src"] = sourceLocationToString(_info.first->location);
|
||||||
tuple["declaration"] = idOrNull(_info.second.declaration);
|
tuple["declaration"] = idOrNull(_info.second.declaration);
|
||||||
tuple["isSlot"] = Json::Value(_info.second.isSlot);
|
tuple["isSlot"] = Json::Value(_info.second.suffix == "slot");
|
||||||
tuple["isOffset"] = Json::Value(_info.second.isOffset);
|
tuple["isOffset"] = Json::Value(_info.second.suffix == "offset");
|
||||||
|
if (!_info.second.suffix.empty())
|
||||||
|
tuple["suffix"] = Json::Value(_info.second.suffix);
|
||||||
tuple["valueSize"] = Json::Value(Json::LargestUInt(_info.second.valueSize));
|
tuple["valueSize"] = Json::Value(Json::LargestUInt(_info.second.valueSize));
|
||||||
return tuple;
|
return tuple;
|
||||||
}
|
}
|
||||||
|
@ -745,9 +745,9 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
else if (m_context.isStateVariable(decl))
|
else if (m_context.isStateVariable(decl))
|
||||||
{
|
{
|
||||||
auto const& location = m_context.storageLocationOfVariable(*decl);
|
auto const& location = m_context.storageLocationOfVariable(*decl);
|
||||||
if (ref->second.isSlot)
|
if (ref->second.suffix == "slot")
|
||||||
m_context << location.first;
|
m_context << location.first;
|
||||||
else if (ref->second.isOffset)
|
else if (ref->second.suffix == "offset")
|
||||||
m_context << u256(location.second);
|
m_context << u256(location.second);
|
||||||
else
|
else
|
||||||
solAssert(false, "");
|
solAssert(false, "");
|
||||||
@ -755,26 +755,44 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
else if (m_context.isLocalVariable(decl))
|
else if (m_context.isLocalVariable(decl))
|
||||||
{
|
{
|
||||||
unsigned stackDiff = static_cast<unsigned>(_assembly.stackHeight()) - m_context.baseStackOffsetOfVariable(*variable);
|
unsigned stackDiff = static_cast<unsigned>(_assembly.stackHeight()) - m_context.baseStackOffsetOfVariable(*variable);
|
||||||
if (ref->second.isSlot || ref->second.isOffset)
|
if (!ref->second.suffix.empty())
|
||||||
{
|
{
|
||||||
solAssert(variable->type()->dataStoredIn(DataLocation::Storage), "");
|
string const& suffix = ref->second.suffix;
|
||||||
unsigned size = variable->type()->sizeOnStack();
|
if (variable->type()->dataStoredIn(DataLocation::Storage))
|
||||||
if (size == 2)
|
|
||||||
{
|
{
|
||||||
// slot plus offset
|
solAssert(suffix == "offset" || suffix == "slot", "");
|
||||||
if (ref->second.isOffset)
|
unsigned size = variable->type()->sizeOnStack();
|
||||||
|
if (size == 2)
|
||||||
|
{
|
||||||
|
// slot plus offset
|
||||||
|
if (suffix == "offset")
|
||||||
|
stackDiff--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
solAssert(size == 1, "");
|
||||||
|
// only slot, offset is zero
|
||||||
|
if (suffix == "offset")
|
||||||
|
{
|
||||||
|
_assembly.appendConstant(u256(0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (variable->type()->dataStoredIn(DataLocation::CallData))
|
||||||
|
{
|
||||||
|
auto const* arrayType = dynamic_cast<ArrayType const*>(variable->type());
|
||||||
|
solAssert(
|
||||||
|
arrayType && arrayType->isDynamicallySized() && arrayType->dataStoredIn(DataLocation::CallData),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
solAssert(suffix == "offset" || suffix == "length", "");
|
||||||
|
solAssert(variable->type()->sizeOnStack() == 2, "");
|
||||||
|
if (suffix == "length")
|
||||||
stackDiff--;
|
stackDiff--;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
solAssert(false, "");
|
||||||
solAssert(size == 1, "");
|
|
||||||
// only slot, offset is zero
|
|
||||||
if (ref->second.isOffset)
|
|
||||||
{
|
|
||||||
_assembly.appendConstant(u256(0));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
solAssert(variable->type()->sizeOnStack() == 1, "");
|
solAssert(variable->type()->sizeOnStack() == 1, "");
|
||||||
@ -784,7 +802,6 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
errinfo_sourceLocation(_inlineAssembly.location()) <<
|
errinfo_sourceLocation(_inlineAssembly.location()) <<
|
||||||
errinfo_comment("Stack too deep, try removing local variables.")
|
errinfo_comment("Stack too deep, try removing local variables.")
|
||||||
);
|
);
|
||||||
solAssert(variable->type()->sizeOnStack() == 1, "");
|
|
||||||
_assembly.appendInstruction(dupInstruction(stackDiff));
|
_assembly.appendInstruction(dupInstruction(stackDiff));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -792,7 +809,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
}
|
}
|
||||||
else if (auto contract = dynamic_cast<ContractDefinition const*>(decl))
|
else if (auto contract = dynamic_cast<ContractDefinition const*>(decl))
|
||||||
{
|
{
|
||||||
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
|
solAssert(ref->second.suffix.empty(), "");
|
||||||
solAssert(contract->isLibrary(), "");
|
solAssert(contract->isLibrary(), "");
|
||||||
_assembly.appendLinkerSymbol(contract->fullyQualifiedName());
|
_assembly.appendLinkerSymbol(contract->fullyQualifiedName());
|
||||||
}
|
}
|
||||||
@ -803,20 +820,39 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// lvalue context
|
// lvalue context
|
||||||
solAssert(!ref->second.isOffset, "");
|
|
||||||
auto variable = dynamic_cast<VariableDeclaration const*>(decl);
|
auto variable = dynamic_cast<VariableDeclaration const*>(decl);
|
||||||
solAssert(
|
|
||||||
!!variable && m_context.isLocalVariable(variable),
|
|
||||||
"Can only assign to stack variables in inline assembly."
|
|
||||||
);
|
|
||||||
solAssert(variable->type()->sizeOnStack() == 1, "");
|
|
||||||
unsigned stackDiff = static_cast<unsigned>(_assembly.stackHeight()) - m_context.baseStackOffsetOfVariable(*variable) - 1;
|
unsigned stackDiff = static_cast<unsigned>(_assembly.stackHeight()) - m_context.baseStackOffsetOfVariable(*variable) - 1;
|
||||||
if (stackDiff > 16 || stackDiff < 1)
|
string const& suffix = ref->second.suffix;
|
||||||
BOOST_THROW_EXCEPTION(
|
if (variable->type()->dataStoredIn(DataLocation::Storage))
|
||||||
StackTooDeepError() <<
|
{
|
||||||
errinfo_sourceLocation(_inlineAssembly.location()) <<
|
solAssert(
|
||||||
errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.")
|
!!variable && m_context.isLocalVariable(variable),
|
||||||
|
"Can only assign to stack variables in inline assembly."
|
||||||
);
|
);
|
||||||
|
solAssert(variable->type()->sizeOnStack() == 1, "");
|
||||||
|
solAssert(suffix == "slot", "");
|
||||||
|
if (stackDiff > 16 || stackDiff < 1)
|
||||||
|
BOOST_THROW_EXCEPTION(
|
||||||
|
StackTooDeepError() <<
|
||||||
|
errinfo_sourceLocation(_inlineAssembly.location()) <<
|
||||||
|
errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (variable->type()->dataStoredIn(DataLocation::CallData))
|
||||||
|
{
|
||||||
|
auto const* arrayType = dynamic_cast<ArrayType const*>(variable->type());
|
||||||
|
solAssert(
|
||||||
|
arrayType && arrayType->isDynamicallySized() && arrayType->dataStoredIn(DataLocation::CallData),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
solAssert(suffix == "offset" || suffix == "length", "");
|
||||||
|
solAssert(variable->type()->sizeOnStack() == 2, "");
|
||||||
|
if (suffix == "length")
|
||||||
|
stackDiff--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
solAssert(suffix.empty(), "");
|
||||||
|
|
||||||
_assembly.appendInstruction(swapInstruction(stackDiff));
|
_assembly.appendInstruction(swapInstruction(stackDiff));
|
||||||
_assembly.appendInstruction(Instruction::POP);
|
_assembly.appendInstruction(Instruction::POP);
|
||||||
}
|
}
|
||||||
|
@ -68,43 +68,12 @@ struct CopyTranslate: public yul::ASTCopier
|
|||||||
|
|
||||||
yul::Expression operator()(yul::Identifier const& _identifier) override
|
yul::Expression operator()(yul::Identifier const& _identifier) override
|
||||||
{
|
{
|
||||||
|
// The operator() function is only called in lvalue context. In rvalue context,
|
||||||
|
// only translate(yul::Identifier) is called.
|
||||||
if (m_references.count(&_identifier))
|
if (m_references.count(&_identifier))
|
||||||
{
|
return translateReference(_identifier);
|
||||||
auto const& reference = m_references.at(&_identifier);
|
else
|
||||||
auto const varDecl = dynamic_cast<VariableDeclaration const*>(reference.declaration);
|
return ASTCopier::operator()(_identifier);
|
||||||
solUnimplementedAssert(varDecl, "");
|
|
||||||
|
|
||||||
if (reference.isOffset || reference.isSlot)
|
|
||||||
{
|
|
||||||
solAssert(reference.isOffset != reference.isSlot, "");
|
|
||||||
|
|
||||||
string value;
|
|
||||||
if (varDecl->isStateVariable())
|
|
||||||
value =
|
|
||||||
reference.isSlot ?
|
|
||||||
m_context.storageLocationOfStateVariable(*varDecl).first.str() :
|
|
||||||
to_string(m_context.storageLocationOfStateVariable(*varDecl).second);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
solAssert(varDecl->isLocalVariable(), "");
|
|
||||||
if (reference.isSlot)
|
|
||||||
value = IRVariable{*varDecl}.part("slot").name();
|
|
||||||
else if (varDecl->type()->isValueType())
|
|
||||||
value = IRVariable{*varDecl}.part("offset").name();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
solAssert(!IRVariable{*varDecl}.hasPart("offset"), "");
|
|
||||||
value = "0";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isdigit(value.front()))
|
|
||||||
return yul::Literal{_identifier.location, yul::LiteralKind::Number, yul::YulString{value}, {}};
|
|
||||||
else
|
|
||||||
return yul::Identifier{_identifier.location, yul::YulString{value}};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ASTCopier::operator()(_identifier);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
yul::YulString translateIdentifier(yul::YulString _name) override
|
yul::YulString translateIdentifier(yul::YulString _name) override
|
||||||
@ -124,24 +93,73 @@ struct CopyTranslate: public yul::ASTCopier
|
|||||||
if (!m_references.count(&_identifier))
|
if (!m_references.count(&_identifier))
|
||||||
return ASTCopier::translate(_identifier);
|
return ASTCopier::translate(_identifier);
|
||||||
|
|
||||||
auto const& reference = m_references.at(&_identifier);
|
yul::Expression translated = translateReference(_identifier);
|
||||||
auto const varDecl = dynamic_cast<VariableDeclaration const*>(reference.declaration);
|
solAssert(holds_alternative<yul::Identifier>(translated), "");
|
||||||
solUnimplementedAssert(varDecl, "");
|
return get<yul::Identifier>(std::move(translated));
|
||||||
|
|
||||||
solAssert(
|
|
||||||
reference.isOffset == false && reference.isSlot == false,
|
|
||||||
"Should not be called for offset/slot"
|
|
||||||
);
|
|
||||||
auto const& var = m_context.localVariable(*varDecl);
|
|
||||||
solAssert(var.type().sizeOnStack() == 1, "");
|
|
||||||
|
|
||||||
return yul::Identifier{
|
|
||||||
_identifier.location,
|
|
||||||
yul::YulString{var.commaSeparatedList()}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
/// Translates a reference to a local variable, potentially including
|
||||||
|
/// a suffix. Might return a literal, which causes this to be invalid in
|
||||||
|
/// lvalue-context.
|
||||||
|
yul::Expression translateReference(yul::Identifier const& _identifier)
|
||||||
|
{
|
||||||
|
auto const& reference = m_references.at(&_identifier);
|
||||||
|
auto const varDecl = dynamic_cast<VariableDeclaration const*>(reference.declaration);
|
||||||
|
solUnimplementedAssert(varDecl, "");
|
||||||
|
string const& suffix = reference.suffix;
|
||||||
|
|
||||||
|
if (suffix.empty())
|
||||||
|
{
|
||||||
|
auto const& var = m_context.localVariable(*varDecl);
|
||||||
|
solAssert(var.type().sizeOnStack() == 1, "");
|
||||||
|
|
||||||
|
return yul::Identifier{
|
||||||
|
_identifier.location,
|
||||||
|
yul::YulString{var.commaSeparatedList()}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
string value;
|
||||||
|
if (varDecl->isStateVariable())
|
||||||
|
{
|
||||||
|
if (suffix == "slot")
|
||||||
|
value = m_context.storageLocationOfStateVariable(*varDecl).first.str();
|
||||||
|
else if (suffix == "offset")
|
||||||
|
value = to_string(m_context.storageLocationOfStateVariable(*varDecl).second);
|
||||||
|
else
|
||||||
|
solAssert(false, "");
|
||||||
|
}
|
||||||
|
else if (varDecl->type()->dataStoredIn(DataLocation::Storage))
|
||||||
|
{
|
||||||
|
solAssert(suffix == "slot" || suffix == "offset", "");
|
||||||
|
solAssert(varDecl->isLocalVariable(), "");
|
||||||
|
if (suffix == "slot")
|
||||||
|
value = IRVariable{*varDecl}.part("slot").name();
|
||||||
|
else if (varDecl->type()->isValueType())
|
||||||
|
value = IRVariable{*varDecl}.part("offset").name();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
solAssert(!IRVariable{*varDecl}.hasPart("offset"), "");
|
||||||
|
value = "0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (varDecl->type()->dataStoredIn(DataLocation::CallData))
|
||||||
|
{
|
||||||
|
solAssert(suffix == "offset" || suffix == "length", "");
|
||||||
|
value = IRVariable{*varDecl}.part(suffix).name();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
solAssert(false, "");
|
||||||
|
|
||||||
|
if (isdigit(value.front()))
|
||||||
|
return yul::Literal{_identifier.location, yul::LiteralKind::Number, yul::YulString{value}, {}};
|
||||||
|
else
|
||||||
|
return yul::Identifier{_identifier.location, yul::YulString{value}};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
yul::Dialect const& m_dialect;
|
yul::Dialect const& m_dialect;
|
||||||
IRGenerationContext& m_context;
|
IRGenerationContext& m_context;
|
||||||
ExternalRefsMap const& m_references;
|
ExternalRefsMap const& m_references;
|
||||||
|
@ -180,6 +180,7 @@
|
|||||||
"isOffset": true,
|
"isOffset": true,
|
||||||
"isSlot": false,
|
"isSlot": false,
|
||||||
"src": "106:8:1",
|
"src": "106:8:1",
|
||||||
|
"suffix": "offset",
|
||||||
"valueSize": 1
|
"valueSize": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -187,6 +188,7 @@
|
|||||||
"isOffset": false,
|
"isOffset": false,
|
||||||
"isSlot": true,
|
"isSlot": true,
|
||||||
"src": "128:6:1",
|
"src": "128:6:1",
|
||||||
|
"suffix": "slot",
|
||||||
"valueSize": 1
|
"valueSize": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -168,6 +168,7 @@
|
|||||||
"isOffset": true,
|
"isOffset": true,
|
||||||
"isSlot": false,
|
"isSlot": false,
|
||||||
"src": "106:8:1",
|
"src": "106:8:1",
|
||||||
|
"suffix": "offset",
|
||||||
"valueSize": 1
|
"valueSize": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -175,6 +176,7 @@
|
|||||||
"isOffset": false,
|
"isOffset": false,
|
||||||
"isSlot": true,
|
"isSlot": true,
|
||||||
"src": "128:6:1",
|
"src": "128:6:1",
|
||||||
|
"suffix": "slot",
|
||||||
"valueSize": 1
|
"valueSize": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
contract C {
|
||||||
|
function f(uint[2][] calldata x) public returns (uint[2][] memory r) {
|
||||||
|
assembly { x.offset := 0x44 x.length := 2 }
|
||||||
|
r = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// f(uint256[2][]): 0x0, 1, 8, 7, 6, 5 -> 0x20, 2, 8, 7, 6, 5
|
@ -0,0 +1,12 @@
|
|||||||
|
contract C {
|
||||||
|
function f(uint[2][] calldata x) public returns (uint o, uint l, uint s) {
|
||||||
|
assembly { l := x.length o := x.offset }
|
||||||
|
uint[2] calldata t = x[1];
|
||||||
|
// statically-sized arrays only use one slot, so we read directly.
|
||||||
|
assembly { s := t }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// f(uint256[2][]): 0x20, 2, 1, 2, 3, 4 -> 0x44, 2, 0x84
|
@ -0,0 +1,10 @@
|
|||||||
|
contract C {
|
||||||
|
function f(bytes calldata x) public returns (bytes memory) {
|
||||||
|
assembly { x.offset := 1 x.length := 3 }
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// f(bytes): 0x20, 0, 0 -> 0x20, 3, 0x5754f80000000000000000000000000000000000000000000000000000000000
|
@ -0,0 +1,9 @@
|
|||||||
|
contract C {
|
||||||
|
function f() public pure returns (bytes calldata x) {
|
||||||
|
assembly { x.offset := 0 x.length := 4 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// f() -> 0x20, 4, 0x26121ff000000000000000000000000000000000000000000000000000000000
|
@ -0,0 +1,18 @@
|
|||||||
|
contract C {
|
||||||
|
function lenBytesRead(bytes calldata x) public returns (uint l) {
|
||||||
|
assembly { l := x.length }
|
||||||
|
}
|
||||||
|
|
||||||
|
function lenStringRead(string calldata x) public returns (uint l) {
|
||||||
|
assembly { l := x.length }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// lenBytesRead(bytes): 0x20, 4, "abcd" -> 4
|
||||||
|
// lenBytesRead(bytes): 0x20, 0, "abcd" -> 0x00
|
||||||
|
// lenBytesRead(bytes): 0x20, 0x21, "abcd", "ef" -> 33
|
||||||
|
// lenStringRead(string): 0x20, 4, "abcd" -> 4
|
||||||
|
// lenStringRead(string): 0x20, 0, "abcd" -> 0x00
|
||||||
|
// lenStringRead(string): 0x20, 0x21, "abcd", "ef" -> 33
|
@ -0,0 +1,19 @@
|
|||||||
|
contract C {
|
||||||
|
function f(bytes calldata x) public returns (uint r) {
|
||||||
|
assembly { r := x.offset }
|
||||||
|
}
|
||||||
|
|
||||||
|
function f(uint, bytes calldata x, uint) public returns (uint r, uint v) {
|
||||||
|
assembly {
|
||||||
|
r := x.offset
|
||||||
|
v := x.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// f(bytes): 0x20, 0, 0 -> 0x44
|
||||||
|
// f(bytes): 0x22, 0, 0, 0 -> 0x46
|
||||||
|
// f(uint256,bytes,uint256): 7, 0x60, 8, 2, 0 -> 0x84, 2
|
||||||
|
// f(uint256,bytes,uint256): 0, 0, 0 -> 0x24, 0x00
|
@ -0,0 +1,18 @@
|
|||||||
|
contract C {
|
||||||
|
function f(uint, bytes calldata x, uint) public returns (uint r, uint v) {
|
||||||
|
assembly {
|
||||||
|
x.offset := 8
|
||||||
|
x.length := 20
|
||||||
|
}
|
||||||
|
assembly {
|
||||||
|
r := x.offset
|
||||||
|
v := x.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// f(uint256,bytes,uint256): 7, 0x60, 8, 2, 0 -> 8, 0x14
|
||||||
|
// f(uint256,bytes,uint256): 0, 0, 0 -> 8, 0x14
|
@ -25,6 +25,8 @@ contract C {
|
|||||||
return data().a;
|
return data().a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
// ----
|
// ----
|
||||||
// get() -> 0
|
// get() -> 0
|
||||||
// mappingAccess(uint256): 1 -> 0, 0
|
// mappingAccess(uint256): 1 -> 0, 0
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
contract C {
|
||||||
|
function f(uint[] calldata bytesAsCalldata) external {
|
||||||
|
assembly {
|
||||||
|
let x := bytesAsCalldata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError 1397: (112-127): Call data elements cannot be accessed directly. Use ".offset" and ".length" to access the calldata offset and length of this array and then use "calldatacopy".
|
@ -0,0 +1,8 @@
|
|||||||
|
contract C {
|
||||||
|
function f(uint[] calldata bytesAsCalldata) external pure {
|
||||||
|
assembly {
|
||||||
|
let x := bytesAsCalldata.offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
@ -0,0 +1,9 @@
|
|||||||
|
contract C {
|
||||||
|
function f(bytes calldata bytesAsCalldata) external {
|
||||||
|
assembly {
|
||||||
|
let x := bytesAsCalldata.slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError 1536: (111-131): Calldata variables only support ".offset" and ".length".
|
@ -6,4 +6,4 @@ contract C {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// TypeError 2370: (111-126): Call data elements cannot be accessed directly. Copy to a local variable first or use "calldataload" or "calldatacopy" with manually determined offsets and sizes.
|
// TypeError 1397: (111-126): Call data elements cannot be accessed directly. Use ".offset" and ".length" to access the calldata offset and length of this array and then use "calldatacopy".
|
||||||
|
@ -7,4 +7,4 @@ contract test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// TypeError 1408: (89-90): Only local variables are supported. To access storage variables, use the .slot and .offset suffixes.
|
// TypeError 1408: (89-90): Only local variables are supported. To access storage variables, use the ".slot" and ".offset" suffixes.
|
||||||
|
@ -10,4 +10,4 @@ contract test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// TypeError 1408: (80-81): Only local variables are supported. To access storage variables, use the .slot and .offset suffixes.
|
// TypeError 1408: (80-81): Only local variables are supported. To access storage variables, use the ".slot" and ".offset" suffixes.
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
contract test {
|
||||||
|
uint x = 1;
|
||||||
|
function f() public {
|
||||||
|
assembly {
|
||||||
|
let t := x.length
|
||||||
|
x.length := 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError 4656: (98-106): State variables only support ".slot" and ".offset".
|
||||||
|
// TypeError 4656: (119-127): State variables only support ".slot" and ".offset".
|
@ -5,4 +5,4 @@ contract c {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// TypeError 1408: (75-76): Only local variables are supported. To access storage variables, use the .slot and .offset suffixes.
|
// TypeError 1408: (75-76): Only local variables are supported. To access storage variables, use the ".slot" and ".offset" suffixes.
|
||||||
|
@ -8,4 +8,4 @@ contract C {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// TypeError 9068: (118-119): You have to use the .slot or .offset suffix to access storage reference variables.
|
// TypeError 9068: (118-119): You have to use the ".slot" or ".offset" suffix to access storage reference variables.
|
||||||
|
@ -9,5 +9,5 @@ contract C {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// DeclarationError 9467: (118-124): Identifier not found. Use ``.slot`` and ``.offset`` to access storage variables.
|
// DeclarationError 9467: (118-124): Identifier not found. Use ".slot" and ".offset" to access storage variables.
|
||||||
// DeclarationError 9467: (142-150): Identifier not found. Use ``.slot`` and ``.offset`` to access storage variables.
|
// DeclarationError 9467: (142-150): Identifier not found. Use ".slot" and ".offset" to access storage variables.
|
||||||
|
@ -6,4 +6,4 @@ contract C {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// TypeError 7944: (84-90): The suffixes .offset and .slot can only be used on storage variables.
|
// TypeError 7944: (84-90): The suffixes ".offset", ".slot" and ".length" can only be used with variables.
|
||||||
|
@ -9,5 +9,5 @@ contract C {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// TypeError 3622: (117-123): The suffixes .offset and .slot can only be used on storage variables.
|
// TypeError 3622: (117-123): The suffix ".slot" is not supported by this variable or type.
|
||||||
// TypeError 3622: (141-149): The suffixes .offset and .slot can only be used on storage variables.
|
// TypeError 3622: (141-149): The suffix ".offset" is not supported by this variable or type.
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
contract C {
|
||||||
|
function f() pure external {
|
||||||
|
function() external two_stack_slots;
|
||||||
|
assembly {
|
||||||
|
let x := two_stack_slots
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError 9857: (132-147): Only types that use one stack slot are supported.
|
Loading…
Reference in New Issue
Block a user