Merge pull request #10208 from ethereum/offsetLengthCalldata

Support .offset and .length for dynamic calldata arrays
This commit is contained in:
chriseth 2020-11-18 12:09:11 +01:00 committed by GitHub
commit 9f0a631948
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 366 additions and 128 deletions

View File

@ -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)

View File

@ -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::

View File

@ -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();
} }

View File

@ -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)

View File

@ -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);
}; };

View File

@ -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;
} }

View File

@ -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);
} }

View File

@ -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;

View File

@ -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
} }
], ],

View File

@ -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
} }
], ],

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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".

View File

@ -0,0 +1,8 @@
contract C {
function f(uint[] calldata bytesAsCalldata) external pure {
assembly {
let x := bytesAsCalldata.offset
}
}
}
// ----

View File

@ -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".

View File

@ -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".

View File

@ -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.

View File

@ -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.

View File

@ -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".

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.