diff --git a/Changelog.md b/Changelog.md index 5c5d9d205..87678abbf 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Language Features: * 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. 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. * 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) diff --git a/docs/assembly.rst b/docs/assembly.rst index 778f90fe5..3e83fa75f 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -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``. 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: .. code:: diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 75445f229..cd0d4852e 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -215,22 +215,21 @@ void ReferencesResolver::operator()(yul::FunctionDefinition const& _function) void ReferencesResolver::operator()(yul::Identifier const& _identifier) { - bool isSlot = boost::algorithm::ends_with(_identifier.name.str(), ".slot"); - bool isOffset = boost::algorithm::ends_with(_identifier.name.str(), ".offset"); + static set suffixes{"slot", "offset", "length"}; + string suffix; + for (string const& s: suffixes) + if (boost::algorithm::ends_with(_identifier.name.str(), "." + s)) + suffix = s; // Could also use `pathFromCurrentScope`, split by '.' auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str()); - if (isSlot || isOffset) + if (!suffix.empty()) { // special mode to access storage variables if (!declarations.empty()) // the special identifier exists itself, we should not allow that. return; - string realName = _identifier.name.str().substr(0, _identifier.name.str().size() - ( - isSlot ? - string(".slot").size() : - string(".offset").size() - )); + string realName = _identifier.name.str().substr(0, _identifier.name.str().size() - suffix.size() - 1); solAssert(!realName.empty(), "Empty name."); declarations = m_resolver.nameFromCurrentScope(realName); if (!declarations.empty()) @@ -255,7 +254,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier) m_errorReporter.declarationError( 9467_error, _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; } @@ -270,8 +269,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier) return; } - m_yulAnnotation->externalReferences[&_identifier].isSlot = isSlot; - m_yulAnnotation->externalReferences[&_identifier].isOffset = isOffset; + m_yulAnnotation->externalReferences[&_identifier].suffix = move(suffix); m_yulAnnotation->externalReferences[&_identifier].declaration = declarations.front(); } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index b3d9220b6..fb806a8a2 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -740,7 +740,6 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) InlineAssemblyAnnotation::ExternalIdentifierInfo& identifierInfo = ref->second; Declaration const* declaration = identifierInfo.declaration; solAssert(!!declaration, ""); - bool requiresStorage = identifierInfo.isSlot || identifierInfo.isOffset; if (auto var = dynamic_cast(declaration)) { 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."); 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."); return false; @@ -789,51 +788,80 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) solAssert(!dynamic_cast(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{"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."); - return false; + if (suffix != "slot" && suffix != "offset") + { + 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(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; } - else if (identifierInfo.isOffset) - { - m_errorReporter.typeError(9739_error, _identifier.location, "Only .slot can be assigned to."); - return false; - } - else - solAssert(identifierInfo.isSlot, ""); + } + else + { + m_errorReporter.typeError(3622_error, _identifier.location, "The suffix \"." + suffix + "\" is not supported by this variable or type."); + return false; } } 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; } 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; } else if (var->type()->sizeOnStack() != 1) { - if (var->type()->dataStoredIn(DataLocation::CallData)) - 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."); + if ( + auto const* arrayType = dynamic_cast(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 + { + solAssert(!var->type()->dataStoredIn(DataLocation::CallData), ""); m_errorReporter.typeError(9857_error, _identifier.location, "Only types that use one stack slot are supported."); + } 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; } else if (_context == yul::IdentifierContext::LValue) diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index dbb77aafc..28beb0d45 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -198,8 +198,8 @@ struct InlineAssemblyAnnotation: StatementAnnotation struct ExternalIdentifierInfo { Declaration const* declaration = nullptr; - bool isSlot = false; ///< Whether the storage slot of a variable is queried. - bool isOffset = false; ///< Whether the intra-slot offset of a storage variable is queried. + /// Suffix used, one of "slot", "offset", "length" or empty. + std::string suffix; size_t valueSize = size_t(-1); }; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 43e572069..f95b5c378 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -224,8 +224,10 @@ Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pairlocation); tuple["declaration"] = idOrNull(_info.second.declaration); - tuple["isSlot"] = Json::Value(_info.second.isSlot); - tuple["isOffset"] = Json::Value(_info.second.isOffset); + tuple["isSlot"] = Json::Value(_info.second.suffix == "slot"); + 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)); return tuple; } diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index cc872d009..d339e50b9 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -745,9 +745,9 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) else if (m_context.isStateVariable(decl)) { auto const& location = m_context.storageLocationOfVariable(*decl); - if (ref->second.isSlot) + if (ref->second.suffix == "slot") m_context << location.first; - else if (ref->second.isOffset) + else if (ref->second.suffix == "offset") m_context << u256(location.second); else solAssert(false, ""); @@ -755,26 +755,44 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) else if (m_context.isLocalVariable(decl)) { unsigned stackDiff = static_cast(_assembly.stackHeight()) - m_context.baseStackOffsetOfVariable(*variable); - if (ref->second.isSlot || ref->second.isOffset) + if (!ref->second.suffix.empty()) { - solAssert(variable->type()->dataStoredIn(DataLocation::Storage), ""); - unsigned size = variable->type()->sizeOnStack(); - if (size == 2) + string const& suffix = ref->second.suffix; + if (variable->type()->dataStoredIn(DataLocation::Storage)) { - // slot plus offset - if (ref->second.isOffset) + solAssert(suffix == "offset" || suffix == "slot", ""); + 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(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(size == 1, ""); - // only slot, offset is zero - if (ref->second.isOffset) - { - _assembly.appendConstant(u256(0)); - return; - } - } + solAssert(false, ""); } else solAssert(variable->type()->sizeOnStack() == 1, ""); @@ -784,7 +802,6 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) errinfo_sourceLocation(_inlineAssembly.location()) << errinfo_comment("Stack too deep, try removing local variables.") ); - solAssert(variable->type()->sizeOnStack() == 1, ""); _assembly.appendInstruction(dupInstruction(stackDiff)); } else @@ -792,7 +809,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) } else if (auto contract = dynamic_cast(decl)) { - solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); + solAssert(ref->second.suffix.empty(), ""); solAssert(contract->isLibrary(), ""); _assembly.appendLinkerSymbol(contract->fullyQualifiedName()); } @@ -803,20 +820,39 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) else { // lvalue context - solAssert(!ref->second.isOffset, ""); auto variable = dynamic_cast(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(_assembly.stackHeight()) - m_context.baseStackOffsetOfVariable(*variable) - 1; - 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.") + string const& suffix = ref->second.suffix; + if (variable->type()->dataStoredIn(DataLocation::Storage)) + { + solAssert( + !!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(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(Instruction::POP); } diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 142660749..0ec2856be 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -68,43 +68,12 @@ struct CopyTranslate: public yul::ASTCopier 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)) - { - auto const& reference = m_references.at(&_identifier); - auto const varDecl = dynamic_cast(reference.declaration); - 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); + return translateReference(_identifier); + else + return ASTCopier::operator()(_identifier); } yul::YulString translateIdentifier(yul::YulString _name) override @@ -124,24 +93,73 @@ struct CopyTranslate: public yul::ASTCopier if (!m_references.count(&_identifier)) return ASTCopier::translate(_identifier); - auto const& reference = m_references.at(&_identifier); - auto const varDecl = dynamic_cast(reference.declaration); - solUnimplementedAssert(varDecl, ""); - - 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()} - }; + yul::Expression translated = translateReference(_identifier); + solAssert(holds_alternative(translated), ""); + return get(std::move(translated)); } 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(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; IRGenerationContext& m_context; ExternalRefsMap const& m_references; diff --git a/test/libsolidity/ASTJSON/assembly/slot_offset.json b/test/libsolidity/ASTJSON/assembly/slot_offset.json index 0858fe617..fb2d22008 100644 --- a/test/libsolidity/ASTJSON/assembly/slot_offset.json +++ b/test/libsolidity/ASTJSON/assembly/slot_offset.json @@ -180,6 +180,7 @@ "isOffset": true, "isSlot": false, "src": "106:8:1", + "suffix": "offset", "valueSize": 1 }, { @@ -187,6 +188,7 @@ "isOffset": false, "isSlot": true, "src": "128:6:1", + "suffix": "slot", "valueSize": 1 } ], diff --git a/test/libsolidity/ASTJSON/assembly/slot_offset_legacy.json b/test/libsolidity/ASTJSON/assembly/slot_offset_legacy.json index 50747baaa..2f13dbf07 100644 --- a/test/libsolidity/ASTJSON/assembly/slot_offset_legacy.json +++ b/test/libsolidity/ASTJSON/assembly/slot_offset_legacy.json @@ -168,6 +168,7 @@ "isOffset": true, "isSlot": false, "src": "106:8:1", + "suffix": "offset", "valueSize": 1 }, { @@ -175,6 +176,7 @@ "isOffset": false, "isSlot": true, "src": "128:6:1", + "suffix": "slot", "valueSize": 1 } ], diff --git a/test/libsolidity/semanticTests/inlineAssembly/calldata_array_assign.sol b/test/libsolidity/semanticTests/inlineAssembly/calldata_array_assign.sol new file mode 100644 index 000000000..631e61fe0 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/calldata_array_assign.sol @@ -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 diff --git a/test/libsolidity/semanticTests/inlineAssembly/calldata_array_read.sol b/test/libsolidity/semanticTests/inlineAssembly/calldata_array_read.sol new file mode 100644 index 000000000..bf5a2e75c --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/calldata_array_read.sol @@ -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 diff --git a/test/libsolidity/semanticTests/inlineAssembly/calldata_assign.sol b/test/libsolidity/semanticTests/inlineAssembly/calldata_assign.sol new file mode 100644 index 000000000..f17dd975c --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/calldata_assign.sol @@ -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 diff --git a/test/libsolidity/semanticTests/inlineAssembly/calldata_assign_from_nowhere.sol b/test/libsolidity/semanticTests/inlineAssembly/calldata_assign_from_nowhere.sol new file mode 100644 index 000000000..1b559f27b --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/calldata_assign_from_nowhere.sol @@ -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 diff --git a/test/libsolidity/semanticTests/inlineAssembly/calldata_length_read.sol b/test/libsolidity/semanticTests/inlineAssembly/calldata_length_read.sol new file mode 100644 index 000000000..e9d722212 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/calldata_length_read.sol @@ -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 diff --git a/test/libsolidity/semanticTests/inlineAssembly/calldata_offset_read.sol b/test/libsolidity/semanticTests/inlineAssembly/calldata_offset_read.sol new file mode 100644 index 000000000..77550ae24 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/calldata_offset_read.sol @@ -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 diff --git a/test/libsolidity/semanticTests/inlineAssembly/calldata_offset_read_write.sol b/test/libsolidity/semanticTests/inlineAssembly/calldata_offset_read_write.sol new file mode 100644 index 000000000..37711e9d5 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/calldata_offset_read_write.sol @@ -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 diff --git a/test/libsolidity/semanticTests/inlineAssembly/slot_access.sol b/test/libsolidity/semanticTests/inlineAssembly/slot_access.sol index abfc6d09b..a1f9874eb 100644 --- a/test/libsolidity/semanticTests/inlineAssembly/slot_access.sol +++ b/test/libsolidity/semanticTests/inlineAssembly/slot_access.sol @@ -25,6 +25,8 @@ contract C { return data().a; } } +// ==== +// compileViaYul: also // ---- // get() -> 0 // mappingAccess(uint256): 1 -> 0, 0 diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/calldata_array.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/calldata_array.sol new file mode 100644 index 000000000..5262b68ec --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/calldata_array.sol @@ -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". diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/calldata_array_offset.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/calldata_array_offset.sol new file mode 100644 index 000000000..b6a2844f7 --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/calldata_array_offset.sol @@ -0,0 +1,8 @@ +contract C { + function f(uint[] calldata bytesAsCalldata) external pure { + assembly { + let x := bytesAsCalldata.offset + } + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/calldata_slot.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/calldata_slot.sol new file mode 100644 index 000000000..4af7fa91e --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/calldata_slot.sol @@ -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". diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/calldata_variables.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/calldata_variables.sol index a7d9da8df..07562ad27 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/invalid/calldata_variables.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/calldata_variables.sol @@ -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". diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/storage_assignment.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/storage_assignment.sol index f833d1740..b01f1ee21 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/invalid/storage_assignment.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/storage_assignment.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/storage_assignment_in_modifier.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/storage_assignment_in_modifier.sol index a86fdc14b..64cfc8623 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/invalid/storage_assignment_in_modifier.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/storage_assignment_in_modifier.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/storage_nonslot.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/storage_nonslot.sol new file mode 100644 index 000000000..ba0ef195c --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/storage_nonslot.sol @@ -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". diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/unbalanced_two_stack_load.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/unbalanced_two_stack_load.sol index 9f51fe572..b1f8ba435 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/invalid/unbalanced_two_stack_load.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/unbalanced_two_stack_load.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/storage_reference.sol b/test/libsolidity/syntaxTests/inlineAssembly/storage_reference.sol index 91ea7aea4..47f898019 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/storage_reference.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/storage_reference.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/storage_reference_old.sol b/test/libsolidity/syntaxTests/inlineAssembly/storage_reference_old.sol index 4b50fbd6e..d2ca4d7cb 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/storage_reference_old.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/storage_reference_old.sol @@ -9,5 +9,5 @@ contract C { } } // ---- -// 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: (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. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/storage_reference_on_function.sol b/test/libsolidity/syntaxTests/inlineAssembly/storage_reference_on_function.sol index b5ac0b1d8..66bf81e6f 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/storage_reference_on_function.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/storage_reference_on_function.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/storage_reference_on_memory.sol b/test/libsolidity/syntaxTests/inlineAssembly/storage_reference_on_memory.sol index d85bc8c28..a319c597c 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/storage_reference_on_memory.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/storage_reference_on_memory.sol @@ -9,5 +9,5 @@ contract C { } } // ---- -// TypeError 3622: (117-123): The suffixes .offset and .slot can only be used on storage variables. -// TypeError 3622: (141-149): 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 suffix ".offset" is not supported by this variable or type. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/two_stack_slot_access.sol b/test/libsolidity/syntaxTests/inlineAssembly/two_stack_slot_access.sol new file mode 100644 index 000000000..5b71b61f9 --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/two_stack_slot_access.sol @@ -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.