mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge remote-tracking branch 'origin/develop' into HEAD
This commit is contained in:
		
						commit
						14352ed921
					
				
							
								
								
									
										10
									
								
								Changelog.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								Changelog.md
									
									
									
									
									
								
							| @ -7,6 +7,16 @@ Breaking Changes: | ||||
| 
 | ||||
| ### 0.7.4 (unreleased) | ||||
| 
 | ||||
| Language Features: | ||||
|  * Constants can be defined at file level. | ||||
| 
 | ||||
| 
 | ||||
| Compiler Features: | ||||
| 
 | ||||
| 
 | ||||
| Bugfixes: | ||||
|  * Type Checker: Fix internal compiler error caused by storage parameters with nested mappings in libraries. | ||||
| 
 | ||||
| 
 | ||||
| ### 0.7.3 (2020-10-07) | ||||
| 
 | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| .. index:: ! constant | ||||
| 
 | ||||
| .. _constants: | ||||
| 
 | ||||
| ************************************** | ||||
| Constant and Immutable State Variables | ||||
| ************************************** | ||||
| @ -9,6 +11,8 @@ In both cases, the variables cannot be modified after the contract has been cons | ||||
| For ``constant`` variables, the value has to be fixed at compile-time, while | ||||
| for ``immutable``, it can still be assigned at construction time. | ||||
| 
 | ||||
| It is also possible to define ``constant`` variables at the file level. | ||||
| 
 | ||||
| The compiler does not reserve a storage slot for these variables, and every occurrence is | ||||
| replaced by the respective value. | ||||
| 
 | ||||
| @ -26,10 +30,11 @@ Not all types for constants and immutables are implemented at this time. The onl | ||||
| :: | ||||
| 
 | ||||
|     // SPDX-License-Identifier: GPL-3.0 | ||||
|     pragma solidity >=0.7.0 <0.9.0; | ||||
|     pragma solidity >0.7.2 <0.9.0; | ||||
| 
 | ||||
|     uint constant X = 32**22 + 8; | ||||
| 
 | ||||
|     contract C { | ||||
|         uint constant X = 32**22 + 8; | ||||
|         string constant TEXT = "abc"; | ||||
|         bytes32 constant MY_HASH = keccak256("abc"); | ||||
|         uint immutable decimals; | ||||
|  | ||||
| @ -7,7 +7,7 @@ options { tokenVocab=SolidityLexer; } | ||||
| 
 | ||||
| /** | ||||
|  * On top level, Solidity allows pragmas, import directives, and | ||||
|  * definitions of contracts, interfaces, libraries, structs and enums. | ||||
|  * definitions of contracts, interfaces, libraries, structs, enums and constants. | ||||
|  */ | ||||
| sourceUnit: ( | ||||
| 	pragmaDirective | ||||
| @ -16,6 +16,7 @@ sourceUnit: ( | ||||
| 	| interfaceDefinition | ||||
| 	| libraryDefinition | ||||
| 	| functionDefinition | ||||
| 	| constantVariableDeclaration | ||||
| 	| structDefinition | ||||
| 	| enumDefinition | ||||
| )* EOF; | ||||
| @ -240,6 +241,17 @@ locals [boolean constantnessSet = false, boolean visibilitySet = false, boolean | ||||
| 	(Assign initialValue=expression)? | ||||
| 	Semicolon; | ||||
| 
 | ||||
| /** | ||||
|  * The declaration of a constant variable. | ||||
|  */ | ||||
| constantVariableDeclaration | ||||
| : | ||||
| 	type=typeName | ||||
| 	Constant | ||||
| 	name=identifier | ||||
| 	Assign initialValue=expression | ||||
| 	Semicolon; | ||||
| 
 | ||||
| /** | ||||
|  * Parameter of an event. | ||||
|  */ | ||||
|  | ||||
| @ -5,7 +5,8 @@ Layout of a Solidity Source File | ||||
| Source files can contain an arbitrary number of | ||||
| :ref:`contract definitions<contract_structure>`, import_ directives, | ||||
| :ref:`pragma directives<pragma>` and | ||||
| :ref:`struct<structs>`, :ref:`enum<enums>` and :ref:`function<functions>` definitions. | ||||
| :ref:`struct<structs>`, :ref:`enum<enums>`, :ref:`function<functions>` | ||||
| and :ref:`constant variable<constants>` definitions. | ||||
| 
 | ||||
| .. index:: ! license, spdx | ||||
| 
 | ||||
|  | ||||
| @ -105,7 +105,7 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ | ||||
| 
 | ||||
| 	if (!_ref.multiline) | ||||
| 	{ | ||||
| 		auto const locationLength = static_cast<size_t>(_ref.endColumn - _ref.startColumn); | ||||
| 		size_t const locationLength = static_cast<size_t>(_ref.endColumn - _ref.startColumn); | ||||
| 
 | ||||
| 		// line 1:
 | ||||
| 		m_stream << leftpad << ' '; | ||||
| @ -124,7 +124,11 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ | ||||
| 		frameColored() << '|'; | ||||
| 
 | ||||
| 		m_stream << ' ' << replaceNonTabs(text.substr(0, static_cast<size_t>(_ref.startColumn)), ' '); | ||||
| 		diagColored() << replaceNonTabs(text.substr(static_cast<size_t>(_ref.startColumn), locationLength), '^'); | ||||
| 		diagColored() << ( | ||||
| 			locationLength == 0 ? | ||||
| 			"^" : | ||||
| 			replaceNonTabs(text.substr(static_cast<size_t>(_ref.startColumn), locationLength), '^') | ||||
| 		); | ||||
| 		m_stream << '\n'; | ||||
| 	} | ||||
| 	else | ||||
|  | ||||
| @ -276,11 +276,17 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable) | ||||
| 	if (_variable.annotation().type) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (_variable.isConstant() && !_variable.isStateVariable()) | ||||
| 	if (_variable.isFileLevelVariable() && !_variable.isConstant()) | ||||
| 		m_errorReporter.declarationError( | ||||
| 			8342_error, | ||||
| 			_variable.location(), | ||||
| 			"Only constant variables are allowed at file level." | ||||
| 		); | ||||
| 	if (_variable.isConstant() && (!_variable.isStateVariable() && !_variable.isFileLevelVariable())) | ||||
| 		m_errorReporter.declarationError( | ||||
| 			1788_error, | ||||
| 			_variable.location(), | ||||
| 			"The \"constant\" keyword can only be used for state variables." | ||||
| 			"The \"constant\" keyword can only be used for state variables or variables at file level." | ||||
| 		); | ||||
| 	if (_variable.immutable() && !_variable.isStateVariable()) | ||||
| 		m_errorReporter.declarationError( | ||||
| @ -344,6 +350,11 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable) | ||||
| 		solAssert(varLoc == Location::Unspecified, ""); | ||||
| 		typeLoc = DataLocation::Memory; | ||||
| 	} | ||||
| 	else if (_variable.isFileLevelVariable()) | ||||
| 	{ | ||||
| 		solAssert(varLoc == Location::Unspecified, ""); | ||||
| 		typeLoc = DataLocation::Memory; | ||||
| 	} | ||||
| 	else if (_variable.isStateVariable()) | ||||
| 	{ | ||||
| 		solAssert(varLoc == Location::Unspecified, ""); | ||||
|  | ||||
| @ -87,7 +87,7 @@ bool DocStringAnalyser::visit(FunctionDefinition const& _function) | ||||
| 
 | ||||
| bool DocStringAnalyser::visit(VariableDeclaration const& _variable) | ||||
| { | ||||
| 	if (!_variable.isStateVariable()) | ||||
| 	if (!_variable.isStateVariable() && !_variable.isFileLevelVariable()) | ||||
| 		return false; | ||||
| 
 | ||||
| 	if (CallableDeclaration const* baseFunction = resolveInheritDoc(_variable.annotation().baseFunctions, _variable, _variable.annotation())) | ||||
|  | ||||
| @ -61,13 +61,13 @@ bool DocStringTagParser::visit(VariableDeclaration const& _variable) | ||||
| { | ||||
| 	if (_variable.isStateVariable()) | ||||
| 	{ | ||||
| 		static set<string> const validPublicTags = set<string>{"dev", "notice", "return", "inheritdoc"}; | ||||
| 		static set<string> const validNonPublicTags = set<string>{"dev", "inheritdoc"}; | ||||
| 		if (_variable.isPublic()) | ||||
| 			parseDocStrings(_variable, _variable.annotation(), validPublicTags, "public state variables"); | ||||
| 			parseDocStrings(_variable, _variable.annotation(), {"dev", "notice", "return", "inheritdoc"}, "public state variables"); | ||||
| 		else | ||||
| 			parseDocStrings(_variable, _variable.annotation(), validNonPublicTags, "non-public state variables"); | ||||
| 			parseDocStrings(_variable, _variable.annotation(), {"dev", "inheritdoc"}, "non-public state variables"); | ||||
| 	} | ||||
| 	else if (_variable.isFileLevelVariable()) | ||||
| 		parseDocStrings(_variable, _variable.annotation(), {"dev"}, "file-level variables"); | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -130,8 +130,11 @@ bool NameAndTypeResolver::resolveNamesAndTypes(SourceUnit& _source) | ||||
| 	try | ||||
| 	{ | ||||
| 		for (shared_ptr<ASTNode> const& node: _source.nodes()) | ||||
| 		{ | ||||
| 			setScope(&_source); | ||||
| 			if (!resolveNamesAndTypesInternal(*node, true)) | ||||
| 				return false; | ||||
| 		} | ||||
| 	} | ||||
| 	catch (langutil::FatalError const&) | ||||
| 	{ | ||||
|  | ||||
| @ -38,6 +38,13 @@ bool PostTypeChecker::check(ASTNode const& _astRoot) | ||||
| 	return Error::containsOnlyWarnings(m_errorReporter.errors()); | ||||
| } | ||||
| 
 | ||||
| bool PostTypeChecker::finalize() | ||||
| { | ||||
| 	for (auto& checker: m_checkers) | ||||
| 		checker->finalize(); | ||||
| 	return Error::containsOnlyWarnings(m_errorReporter.errors()); | ||||
| } | ||||
| 
 | ||||
| bool PostTypeChecker::visit(ContractDefinition const& _contractDefinition) | ||||
| { | ||||
| 	return callVisit(_contractDefinition); | ||||
| @ -83,6 +90,11 @@ bool PostTypeChecker::visit(Identifier const& _identifier) | ||||
| 	return callVisit(_identifier); | ||||
| } | ||||
| 
 | ||||
| bool PostTypeChecker::visit(MemberAccess const& _memberAccess) | ||||
| { | ||||
| 	return callVisit(_memberAccess); | ||||
| } | ||||
| 
 | ||||
| bool PostTypeChecker::visit(StructDefinition const& _struct) | ||||
| { | ||||
| 	return callVisit(_struct); | ||||
| @ -110,14 +122,7 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker | ||||
| 	ConstStateVarCircularReferenceChecker(ErrorReporter& _errorReporter): | ||||
| 		Checker(_errorReporter) {} | ||||
| 
 | ||||
| 	bool visit(ContractDefinition const&) override | ||||
| 	{ | ||||
| 		solAssert(!m_currentConstVariable, ""); | ||||
| 		solAssert(m_constVariableDependencies.empty(), ""); | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	void endVisit(ContractDefinition const&) override | ||||
| 	void finalize() override | ||||
| 	{ | ||||
| 		solAssert(!m_currentConstVariable, ""); | ||||
| 		for (auto declaration: m_constVariables) | ||||
| @ -128,9 +133,12 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker | ||||
| 					"The value of the constant " + declaration->name() + | ||||
| 					" has a cyclic dependency via " + identifier->name() + "." | ||||
| 				); | ||||
| 	} | ||||
| 
 | ||||
| 		m_constVariables.clear(); | ||||
| 		m_constVariableDependencies.clear(); | ||||
| 	bool visit(ContractDefinition const&) override | ||||
| 	{ | ||||
| 		solAssert(!m_currentConstVariable, ""); | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	bool visit(VariableDeclaration const& _variable) override | ||||
| @ -162,6 +170,15 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	bool visit(MemberAccess const& _memberAccess) override | ||||
| 	{ | ||||
| 		if (m_currentConstVariable) | ||||
| 			if (auto var = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration)) | ||||
| 				if (var->isConstant()) | ||||
| 					m_constVariableDependencies[m_currentConstVariable].insert(var); | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	VariableDeclaration const* findCycle(VariableDeclaration const& _startingFrom) | ||||
| 	{ | ||||
| 		auto visitor = [&](VariableDeclaration const& _variable, util::CycleDetector<VariableDeclaration>& _cycleDetector, size_t _depth) | ||||
|  | ||||
| @ -35,7 +35,7 @@ namespace solidity::frontend | ||||
| 
 | ||||
| /**
 | ||||
|  * This module performs analyses on the AST that are done after type checking and assignments of types: | ||||
|  *  - whether there are circular references in constant state variables | ||||
|  *  - whether there are circular references in constant variables | ||||
|  *  - whether override specifiers are actually contracts | ||||
|  *  - whether a modifier is in a function header | ||||
|  *  - whether an event is used outside of an emit statement | ||||
| @ -54,6 +54,9 @@ public: | ||||
| 	{ | ||||
| 		Checker(langutil::ErrorReporter& _errorReporter): | ||||
| 			m_errorReporter(_errorReporter) {} | ||||
| 
 | ||||
| 		/// Called after all source units have been visited.
 | ||||
| 		virtual void finalize() {} | ||||
| 	protected: | ||||
| 		langutil::ErrorReporter& m_errorReporter; | ||||
| 	}; | ||||
| @ -63,6 +66,9 @@ public: | ||||
| 
 | ||||
| 	bool check(ASTNode const& _astRoot); | ||||
| 
 | ||||
| 	/// Called after all source units have been visited.
 | ||||
| 	bool finalize(); | ||||
| 
 | ||||
| private: | ||||
| 	bool visit(ContractDefinition const& _contract) override; | ||||
| 	void endVisit(ContractDefinition const& _contract) override; | ||||
| @ -77,6 +83,7 @@ private: | ||||
| 	bool visit(FunctionCall const& _functionCall) override; | ||||
| 
 | ||||
| 	bool visit(Identifier const& _identifier) override; | ||||
| 	bool visit(MemberAccess const& _identifier) override; | ||||
| 
 | ||||
| 	bool visit(StructDefinition const& _struct) override; | ||||
| 	void endVisit(StructDefinition const& _struct) override; | ||||
|  | ||||
| @ -599,8 +599,13 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) | ||||
| 	if (auto referenceType = dynamic_cast<ReferenceType const*>(varType)) | ||||
| 	{ | ||||
| 		auto result = referenceType->validForLocation(referenceType->location()); | ||||
| 		if (result && (_variable.isConstructorParameter() || _variable.isPublicCallableParameter())) | ||||
| 			result = referenceType->validForLocation(DataLocation::CallData); | ||||
| 		if (result) | ||||
| 		{ | ||||
| 			bool isLibraryStorageParameter = (_variable.isLibraryFunctionParameter() && referenceType->location() == DataLocation::Storage); | ||||
| 			bool callDataCheckRequired = ((_variable.isConstructorParameter() || _variable.isPublicCallableParameter()) && !isLibraryStorageParameter); | ||||
| 			if (callDataCheckRequired) | ||||
| 				result = referenceType->validForLocation(DataLocation::CallData); | ||||
| 		} | ||||
| 		if (!result) | ||||
| 		{ | ||||
| 			solAssert(!result.message().empty(), "Expected detailed error message"); | ||||
|  | ||||
| @ -647,6 +647,11 @@ bool VariableDeclaration::isStateVariable() const | ||||
| 	return dynamic_cast<ContractDefinition const*>(scope()); | ||||
| } | ||||
| 
 | ||||
| bool VariableDeclaration::isFileLevelVariable() const | ||||
| { | ||||
| 	return dynamic_cast<SourceUnit const*>(scope()); | ||||
| } | ||||
| 
 | ||||
| set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() const | ||||
| { | ||||
| 	using Location = VariableDeclaration::Location; | ||||
|  | ||||
| @ -967,6 +967,7 @@ public: | ||||
| 	/// Can only be called after reference resolution.
 | ||||
| 	bool hasReferenceOrMappingType() const; | ||||
| 	bool isStateVariable() const; | ||||
| 	bool isFileLevelVariable() const; | ||||
| 	bool isIndexed() const { return m_isIndexed; } | ||||
| 	Mutability mutability() const { return m_mutability; } | ||||
| 	bool isConstant() const { return m_mutability == Mutability::Constant; } | ||||
|  | ||||
| @ -126,7 +126,6 @@ public: | ||||
| 	/// stack slot, it takes exactly that number of values.
 | ||||
| 	std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false); | ||||
| 
 | ||||
| private: | ||||
| 	struct EncodingOptions | ||||
| 	{ | ||||
| 		/// Pad/signextend value types and bytes/string to multiples of 32 bytes.
 | ||||
| @ -146,6 +145,7 @@ private: | ||||
| 		std::string toFunctionNameSuffix() const; | ||||
| 	}; | ||||
| 
 | ||||
| 	/// Internal encoding function that is also used by some copying routines.
 | ||||
| 	/// @returns the name of the ABI encoding function with the given type
 | ||||
| 	/// and queues the generation of the function to the requested functions.
 | ||||
| 	/// @param _fromStack if false, the input value was just loaded from storage
 | ||||
| @ -155,6 +155,7 @@ private: | ||||
| 		Type const& _targetType, | ||||
| 		EncodingOptions const& _options | ||||
| 	); | ||||
| 	/// Internal encoding function that is also used by some copying routines.
 | ||||
| 	/// @returns the name of a function that internally calls `abiEncodingFunction`
 | ||||
| 	/// but always returns the updated encoding position, even if the type is
 | ||||
| 	/// statically encoded.
 | ||||
| @ -163,6 +164,8 @@ private: | ||||
| 		Type const& _targetType, | ||||
| 		EncodingOptions const& _options | ||||
| 	); | ||||
| 
 | ||||
| private: | ||||
| 	/// Part of @a abiEncodingFunction for array target type and given calldata array.
 | ||||
| 	/// Uses calldatacopy and does not perform cleanup or validation and can therefore only
 | ||||
| 	/// be used for byte arrays and arrays with the base type uint256 or bytes32.
 | ||||
|  | ||||
| @ -1753,18 +1753,24 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) | ||||
| 	{ | ||||
| 		Type::Category category = _memberAccess.annotation().type->category(); | ||||
| 		solAssert( | ||||
| 			dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration) || | ||||
| 			dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration) || | ||||
| 			category == Type::Category::TypeType || | ||||
| 			category == Type::Category::Module || | ||||
| 			category == Type::Category::Function, | ||||
| 			category == Type::Category::Module, | ||||
| 			"" | ||||
| 		); | ||||
| 		if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type)) | ||||
| 		if (auto variable = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration)) | ||||
| 		{ | ||||
| 			auto const* funDef = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration); | ||||
| 			solAssert(funDef && funDef->isFree(), ""); | ||||
| 			solAssert(variable->isConstant(), ""); | ||||
| 			appendVariable(*variable, static_cast<Expression const&>(_memberAccess)); | ||||
| 		} | ||||
| 		else if (auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration)) | ||||
| 		{ | ||||
| 			auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type); | ||||
| 			solAssert(function && function->isFree(), ""); | ||||
| 			solAssert(funType->kind() == FunctionType::Kind::Internal, ""); | ||||
| 			solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, ""); | ||||
| 			utils().pushCombinedFunctionEntryLabel(*funDef); | ||||
| 			utils().pushCombinedFunctionEntryLabel(*function); | ||||
| 		} | ||||
| 		break; | ||||
| 	} | ||||
|  | ||||
| @ -33,8 +33,11 @@ string MultiUseYulFunctionCollector::requestedFunctions() | ||||
| { | ||||
| 	string result; | ||||
| 	for (auto const& f: m_requestedFunctions) | ||||
| 	{ | ||||
| 		solAssert(f.second != "<<STUB<<", ""); | ||||
| 		// std::map guarantees ascending order when iterating through its keys.
 | ||||
| 		result += f.second; | ||||
| 	} | ||||
| 	m_requestedFunctions.clear(); | ||||
| 	return result; | ||||
| } | ||||
| @ -43,9 +46,10 @@ string MultiUseYulFunctionCollector::createFunction(string const& _name, functio | ||||
| { | ||||
| 	if (!m_requestedFunctions.count(_name)) | ||||
| 	{ | ||||
| 		m_requestedFunctions[_name] = "<<STUB<<"; | ||||
| 		string fun = _creator(); | ||||
| 		solAssert(!fun.empty(), ""); | ||||
| 		solAssert(fun.find("function " + _name) != string::npos, "Function not properly named."); | ||||
| 		solAssert(fun.find("function " + _name + "(") != string::npos, "Function not properly named."); | ||||
| 		m_requestedFunctions[_name] = std::move(fun); | ||||
| 	} | ||||
| 	return _name; | ||||
|  | ||||
| @ -851,7 +851,6 @@ std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type) | ||||
| 	solAssert(_type.isDynamicallySized(), ""); | ||||
| 	solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!"); | ||||
| 	solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "..."); | ||||
| 	solUnimplementedAssert(_type.baseType()->storageSize() == 1, ""); | ||||
| 
 | ||||
| 	string functionName = "resize_array_" + _type.identifier(); | ||||
| 	return m_functionCollector.createFunction(functionName, [&]() { | ||||
| @ -1147,6 +1146,45 @@ string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type) | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::clearStorageStructFunction(StructType const& _type) | ||||
| { | ||||
| 	solAssert(_type.location() == DataLocation::Storage, ""); | ||||
| 
 | ||||
| 	string functionName = "clear_struct_storage_" + _type.identifier(); | ||||
| 
 | ||||
| 	return m_functionCollector.createFunction(functionName, [&] { | ||||
| 		MemberList::MemberMap structMembers = _type.nativeMembers(nullptr); | ||||
| 		vector<map<string, string>> memberSetValues; | ||||
| 
 | ||||
| 		for (auto const& member: structMembers) | ||||
| 		{ | ||||
| 			auto const& [memberSlotDiff, memberStorageOffset] = _type.storageOffsetsOfMember(member.name); | ||||
| 
 | ||||
| 			memberSetValues.emplace_back().emplace("clearMember", Whiskers(R"( | ||||
| 					<setZero>(add(slot, <memberSlotDiff>), <memberStorageOffset>) | ||||
| 				)") | ||||
| 				("setZero", storageSetToZeroFunction(*member.type)) | ||||
| 				("memberSlotDiff",  memberSlotDiff.str()) | ||||
| 				("memberStorageOffset", to_string(memberStorageOffset)) | ||||
| 				.render() | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		return Whiskers(R"( | ||||
| 			function <functionName>(slot) { | ||||
| 				<#member> | ||||
| 					<clearMember> | ||||
| 				</member> | ||||
| 			} | ||||
| 		)") | ||||
| 		("functionName", functionName) | ||||
| 		("allocStruct", allocateMemoryStructFunction(_type)) | ||||
| 		("storageSize", _type.storageSize().str()) | ||||
| 		("member", memberSetValues) | ||||
| 		.render(); | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type) | ||||
| { | ||||
| 	string functionName = "array_convert_length_to_size_" + _type.identifier(); | ||||
| @ -1446,6 +1484,70 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::copyArrayFromStorageToMemoryFunction(ArrayType const& _from, ArrayType const& _to) | ||||
| { | ||||
| 	solAssert(_from.dataStoredIn(DataLocation::Storage), ""); | ||||
| 	solAssert(_to.dataStoredIn(DataLocation::Memory), ""); | ||||
| 	solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); | ||||
| 	if (!_from.isDynamicallySized()) | ||||
| 		solAssert(_from.length() == _to.length(), ""); | ||||
| 
 | ||||
| 	string functionName = "copy_array_from_storage_to_memory_" + _from.identifier(); | ||||
| 
 | ||||
| 	return m_functionCollector.createFunction(functionName, [&]() { | ||||
| 		if (_from.baseType()->isValueType()) | ||||
| 		{ | ||||
| 			solAssert(_from.baseType() == _to.baseType(), ""); | ||||
| 			ABIFunctions abi(m_evmVersion, m_revertStrings, m_functionCollector); | ||||
| 			return Whiskers(R"( | ||||
| 				function <functionName>(slot) -> memptr { | ||||
| 					memptr := <allocateTemp>() | ||||
| 					let end := <encode>(slot, memptr) | ||||
| 					mstore(<freeMemoryPointer>, end) | ||||
| 				} | ||||
| 			)") | ||||
| 			("functionName", functionName) | ||||
| 			("allocateTemp", allocationTemporaryMemoryFunction()) | ||||
| 			( | ||||
| 				"encode", | ||||
| 				abi.abiEncodeAndReturnUpdatedPosFunction(_from, _to, ABIFunctions::EncodingOptions{}) | ||||
| 			) | ||||
| 			("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)) | ||||
| 			.render(); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			solAssert(_to.memoryStride() == 32, ""); | ||||
| 			solAssert(_to.baseType()->dataStoredIn(DataLocation::Memory), ""); | ||||
| 			solAssert(_from.baseType()->dataStoredIn(DataLocation::Storage), ""); | ||||
| 			solAssert(!_from.isByteArray(), ""); | ||||
| 			solAssert(*_to.withLocation(DataLocation::Storage, _from.isPointer()) == _from, ""); | ||||
| 			return Whiskers(R"( | ||||
| 				function <functionName>(slot) -> memptr { | ||||
| 					let length := <lengthFunction>(slot) | ||||
| 					memptr := <allocateArray>(length) | ||||
| 					let mpos := memptr | ||||
| 					<?dynamic>mpos := add(mpos, 0x20)</dynamic> | ||||
| 					let spos := <arrayDataArea>(slot) | ||||
| 					for { let i := 0 } lt(i, length) { i := add(i, 1) } { | ||||
| 						mstore(mpos, <convert>(spos)) | ||||
| 						mpos := add(mpos, 0x20) | ||||
| 						spos := add(spos, <baseStorageSize>) | ||||
| 					} | ||||
| 				} | ||||
| 			)") | ||||
| 			("functionName", functionName) | ||||
| 			("lengthFunction", arrayLengthFunction(_from)) | ||||
| 			("allocateArray", allocateMemoryArrayFunction(_to)) | ||||
| 			("arrayDataArea", arrayDataAreaFunction(_from)) | ||||
| 			("dynamic", _to.isDynamicallySized()) | ||||
| 			("convert", conversionFunction(*_from.baseType(), *_to.baseType())) | ||||
| 			("baseStorageSize", _from.baseType()->storageSize().str()) | ||||
| 			.render(); | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType) | ||||
| { | ||||
| 	solAssert(_keyType.sizeOnStack() <= 1, ""); | ||||
| @ -2261,8 +2363,12 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) | ||||
| 					break; | ||||
| 				case DataLocation::Memory: | ||||
| 					// Copy the array to a free position in memory, unless it is already in memory.
 | ||||
| 					solUnimplementedAssert(from.location() == DataLocation::Memory, "Not implemented yet."); | ||||
| 					body = "converted := value"; | ||||
| 					if (from.location() == DataLocation::Memory) | ||||
| 						body = "converted := value"; | ||||
| 					else if (from.location() == DataLocation::CallData) | ||||
| 						solUnimplemented("Conversion of calldata types not yet implemented."); | ||||
| 					else | ||||
| 						body = "converted := " + copyArrayFromStorageToMemoryFunction(from, to) + "(value)"; | ||||
| 					break; | ||||
| 				case DataLocation::CallData: | ||||
| 					solUnimplemented("Conversion of calldata types not yet implemented."); | ||||
| @ -2278,27 +2384,32 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) | ||||
| 			auto const& toStructType = dynamic_cast<StructType const &>(_to); | ||||
| 			solAssert(fromStructType.structDefinition() == toStructType.structDefinition(), ""); | ||||
| 
 | ||||
| 			solUnimplementedAssert(!fromStructType.isDynamicallyEncoded(), ""); | ||||
| 			solUnimplementedAssert(toStructType.location() == DataLocation::Memory, ""); | ||||
| 			solUnimplementedAssert(fromStructType.location() != DataLocation::Memory, ""); | ||||
| 
 | ||||
| 			if (fromStructType.location() == DataLocation::CallData) | ||||
| 			{ | ||||
| 				body = Whiskers(R"( | ||||
| 					converted := <abiDecode>(value, calldatasize()) | ||||
| 				)")("abiDecode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleDecoder( | ||||
| 					{&toStructType} | ||||
| 				)).render(); | ||||
| 			} | ||||
| 			if (fromStructType.location() == toStructType.location() && toStructType.isPointer()) | ||||
| 				body = "converted := value"; | ||||
| 			else | ||||
| 			{ | ||||
| 				solAssert(fromStructType.location() == DataLocation::Storage, ""); | ||||
| 				solUnimplementedAssert(toStructType.location() == DataLocation::Memory, ""); | ||||
| 				solUnimplementedAssert(fromStructType.location() != DataLocation::Memory, ""); | ||||
| 
 | ||||
| 				body = Whiskers(R"( | ||||
| 					converted := <readFromStorage>(value) | ||||
| 				)") | ||||
| 				("readFromStorage", readFromStorage(toStructType, 0, true)) | ||||
| 				.render(); | ||||
| 				if (fromStructType.location() == DataLocation::CallData) | ||||
| 				{ | ||||
| 					solUnimplementedAssert(!fromStructType.isDynamicallyEncoded(), ""); | ||||
| 					body = Whiskers(R"( | ||||
| 						converted := <abiDecode>(value, calldatasize()) | ||||
| 					)")("abiDecode", ABIFunctions(m_evmVersion, m_revertStrings, m_functionCollector).tupleDecoder( | ||||
| 						{&toStructType} | ||||
| 					)).render(); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					solAssert(fromStructType.location() == DataLocation::Storage, ""); | ||||
| 
 | ||||
| 					body = Whiskers(R"( | ||||
| 						converted := <readFromStorage>(value) | ||||
| 					)") | ||||
| 					("readFromStorage", readFromStorage(toStructType, 0, true)) | ||||
| 					.render(); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			break; | ||||
| @ -2441,7 +2552,7 @@ string YulUtilFunctions::cleanupFunction(Type const& _type) | ||||
| 		case Type::Category::Enum: | ||||
| 		{ | ||||
| 			// Out of range enums cannot be truncated unambigiously and therefore it should be an error.
 | ||||
| 			templ("body", "cleaned := value " + validatorFunction(_type) + "(value)"); | ||||
| 			templ("body", "cleaned := value " + validatorFunction(_type, false) + "(value)"); | ||||
| 			break; | ||||
| 		} | ||||
| 		case Type::Category::InaccessibleDynamic: | ||||
| @ -2741,11 +2852,24 @@ string YulUtilFunctions::storageSetToZeroFunction(Type const& _type) | ||||
| 		else if (_type.category() == Type::Category::Array) | ||||
| 			return Whiskers(R"( | ||||
| 				function <functionName>(slot, offset) { | ||||
| 					if iszero(eq(offset, 0)) { <panic>() } | ||||
| 					<clearArray>(slot) | ||||
| 				} | ||||
| 			)") | ||||
| 			("functionName", functionName) | ||||
| 			("clearArray", clearStorageArrayFunction(dynamic_cast<ArrayType const&>(_type))) | ||||
| 			("panic", panicFunction()) | ||||
| 			.render(); | ||||
| 		else if (_type.category() == Type::Category::Struct) | ||||
| 			return Whiskers(R"( | ||||
| 				function <functionName>(slot, offset) { | ||||
| 					if iszero(eq(offset, 0)) { <panic>() } | ||||
| 					<clearStruct>(slot) | ||||
| 				} | ||||
| 			)") | ||||
| 			("functionName", functionName) | ||||
| 			("clearStruct", clearStorageStructFunction(dynamic_cast<StructType const&>(_type))) | ||||
| 			("panic", panicFunction()) | ||||
| 			.render(); | ||||
| 		else | ||||
| 			solUnimplemented("setToZero for type " + _type.identifier() + " not yet implemented!"); | ||||
| @ -2933,7 +3057,7 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC | ||||
| 		)") | ||||
| 		("functionName", functionName) | ||||
| 		("fromCalldata", _fromCalldata) | ||||
| 		("validate", validatorFunction(_type)) | ||||
| 		("validate", validatorFunction(_type, true)) | ||||
| 		// Byte array elements generally need cleanup.
 | ||||
| 		// Other types are cleaned as well to account for dirty memory e.g. due to inline assembly.
 | ||||
| 		("cleanup", cleanupFunction(_type)) | ||||
|  | ||||
| @ -220,6 +220,10 @@ public: | ||||
| 	/// Only works for memory arrays, calldata arrays and storage arrays that every item occupies one or multiple full slots.
 | ||||
| 	std::string nextArrayElementFunction(ArrayType const& _type); | ||||
| 
 | ||||
| 	/// @returns the name of a function that allocates a memory array and copies the contents
 | ||||
| 	/// of the storage array into it.
 | ||||
| 	std::string copyArrayFromStorageToMemoryFunction(ArrayType const& _from, ArrayType const& _to); | ||||
| 
 | ||||
| 	/// @returns the name of a function that performs index access for mappings.
 | ||||
| 	/// @param _mappingType the type of the mapping
 | ||||
| 	/// @param _keyType the type of the value provided
 | ||||
| @ -343,7 +347,7 @@ public: | ||||
| 	/// otherwise an assertion failure.
 | ||||
| 	///
 | ||||
| 	/// This is used for data decoded from external sources.
 | ||||
| 	std::string validatorFunction(Type const& _type, bool _revertOnFailure = false); | ||||
| 	std::string validatorFunction(Type const& _type, bool _revertOnFailure); | ||||
| 
 | ||||
| 	std::string packedHashFunction(std::vector<Type const*> const& _givenTypes, std::vector<Type const*> const& _targetTypes); | ||||
| 
 | ||||
| @ -427,6 +431,10 @@ private: | ||||
| 	/// signature: (slot, offset)
 | ||||
| 	std::string partialClearStorageSlotFunction(); | ||||
| 
 | ||||
| 	/// @returns the name of a function that will clear the given storage struct
 | ||||
| 	/// signature: (slot) ->
 | ||||
| 	std::string clearStorageStructFunction(StructType const& _type); | ||||
| 
 | ||||
| 	langutil::EVMVersion m_evmVersion; | ||||
| 	RevertStrings m_revertStrings; | ||||
| 	MultiUseYulFunctionCollector& m_functionCollector; | ||||
|  | ||||
| @ -1871,6 +1871,35 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) | ||||
| 			solAssert(false, ""); | ||||
| 		break; | ||||
| 	} | ||||
| 	case Type::Category::Module: | ||||
| 	{ | ||||
| 		Type::Category category = _memberAccess.annotation().type->category(); | ||||
| 		solAssert( | ||||
| 			dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration) || | ||||
| 			dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration) || | ||||
| 			category == Type::Category::TypeType || | ||||
| 			category == Type::Category::Module, | ||||
| 			"" | ||||
| 		); | ||||
| 		if (auto variable = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration)) | ||||
| 		{ | ||||
| 			solAssert(variable->isConstant(), ""); | ||||
| 			handleVariableReference(*variable, static_cast<Expression const&>(_memberAccess)); | ||||
| 		} | ||||
| 		else if (auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration)) | ||||
| 		{ | ||||
| 			auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type); | ||||
| 			solAssert(function && function->isFree(), ""); | ||||
| 			solAssert(function->functionType(true), ""); | ||||
| 			solAssert(function->functionType(true)->kind() == FunctionType::Kind::Internal, ""); | ||||
| 			solAssert(funType->kind() == FunctionType::Kind::Internal, ""); | ||||
| 			solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, ""); | ||||
| 
 | ||||
| 			define(_memberAccess) << to_string(function->id()) << "\n"; | ||||
| 			m_context.internalFunctionAccessed(_memberAccess, *function); | ||||
| 		} | ||||
| 		break; | ||||
| 	} | ||||
| 	default: | ||||
| 		solAssert(false, "Member access to unknown type."); | ||||
| 	} | ||||
| @ -2130,6 +2159,10 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier) | ||||
| 	{ | ||||
| 		// no-op
 | ||||
| 	} | ||||
| 	else if (dynamic_cast<ImportDirective const*>(declaration)) | ||||
| 	{ | ||||
| 		// no-op
 | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		solAssert(false, "Identifier type not expected in expression context."); | ||||
| @ -2162,7 +2195,7 @@ void IRGeneratorForStatements::handleVariableReference( | ||||
| ) | ||||
| { | ||||
| 	setLocation(_referencingExpression); | ||||
| 	if (_variable.isStateVariable() && _variable.isConstant()) | ||||
| 	if ((_variable.isStateVariable() || _variable.isFileLevelVariable()) && _variable.isConstant()) | ||||
| 		define(_referencingExpression) << constantValueFunction(_variable) << "()\n"; | ||||
| 	else if (_variable.isStateVariable() && _variable.immutable()) | ||||
| 		setLValue(_referencingExpression, IRLValue{ | ||||
|  | ||||
| @ -387,6 +387,8 @@ bool CompilerStack::analyze() | ||||
| 			for (Source const* source: m_sourceOrder) | ||||
| 				if (source->ast && !postTypeChecker.check(*source->ast)) | ||||
| 					noErrors = false; | ||||
| 			if (!postTypeChecker.finalize()) | ||||
| 				noErrors = false; | ||||
| 		} | ||||
| 
 | ||||
| 		// Check that immutable variables are never read in c'tors and assigned
 | ||||
|  | ||||
| @ -111,7 +111,17 @@ ASTPointer<SourceUnit> Parser::parse(shared_ptr<Scanner> const& _scanner) | ||||
| 				nodes.push_back(parseFunctionDefinition(true)); | ||||
| 				break; | ||||
| 			default: | ||||
| 				fatalParserError(7858_error, "Expected pragma, import directive or contract/interface/library/struct/enum/function definition."); | ||||
| 				// Constant variable.
 | ||||
| 				if (variableDeclarationStart() && m_scanner->peekNextToken() != Token::EOS) | ||||
| 				{ | ||||
| 					VarDeclParserOptions options; | ||||
| 					options.kind = VarDeclKind::FileLevel; | ||||
| 					options.allowInitialValue = true; | ||||
| 					nodes.push_back(parseVariableDeclaration(options)); | ||||
| 					expectToken(Token::Semicolon); | ||||
| 				} | ||||
| 				else | ||||
| 					fatalParserError(7858_error, "Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition."); | ||||
| 			} | ||||
| 		} | ||||
| 		solAssert(m_recursionDepth == 0, ""); | ||||
| @ -332,15 +342,10 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition() | ||||
| 				subNodes.push_back(parseStructDefinition()); | ||||
| 			else if (currentTokenValue == Token::Enum) | ||||
| 				subNodes.push_back(parseEnumDefinition()); | ||||
| 			else if ( | ||||
| 				currentTokenValue == Token::Identifier || | ||||
| 				currentTokenValue == Token::Mapping || | ||||
| 				TokenTraits::isElementaryTypeName(currentTokenValue) || | ||||
| 				(currentTokenValue == Token::Function && m_scanner->peekNextToken() == Token::LParen) | ||||
| 			) | ||||
| 			else if (variableDeclarationStart()) | ||||
| 			{ | ||||
| 				VarDeclParserOptions options; | ||||
| 				options.isStateVariable = true; | ||||
| 				options.kind = VarDeclKind::State; | ||||
| 				options.allowInitialValue = true; | ||||
| 				subNodes.push_back(parseVariableDeclaration(options)); | ||||
| 				expectToken(Token::Semicolon); | ||||
| @ -687,10 +692,10 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( | ||||
| 	ASTPointer<TypeName> type = _lookAheadArrayType ? _lookAheadArrayType : parseTypeName(); | ||||
| 	nodeFactory.setEndPositionFromNode(type); | ||||
| 
 | ||||
| 	if (!_options.isStateVariable && documentation != nullptr) | ||||
| 		parserError(2837_error, "Only state variables can have a docstring."); | ||||
| 	if (_options.kind == VarDeclKind::Other && documentation != nullptr) | ||||
| 		parserError(2837_error, "Only state variables or file-level variables can have a docstring."); | ||||
| 
 | ||||
| 	if (dynamic_cast<FunctionTypeName*>(type.get()) && _options.isStateVariable && m_scanner->currentToken() == Token::LBrace) | ||||
| 	if (dynamic_cast<FunctionTypeName*>(type.get()) && _options.kind == VarDeclKind::State && m_scanner->currentToken() == Token::LBrace) | ||||
| 		fatalParserError( | ||||
| 			2915_error, | ||||
| 			"Expected a state variable declaration. If you intended this as a fallback function " | ||||
| @ -708,7 +713,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( | ||||
| 	while (true) | ||||
| 	{ | ||||
| 		Token token = m_scanner->currentToken(); | ||||
| 		if (_options.isStateVariable && TokenTraits::isVariableVisibilitySpecifier(token)) | ||||
| 		if (_options.kind == VarDeclKind::State && TokenTraits::isVariableVisibilitySpecifier(token)) | ||||
| 		{ | ||||
| 			nodeFactory.markEndPosition(); | ||||
| 			if (visibility != Visibility::Default) | ||||
| @ -724,7 +729,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( | ||||
| 			else | ||||
| 				visibility = parseVisibilitySpecifier(); | ||||
| 		} | ||||
| 		else if (_options.isStateVariable && token == Token::Override) | ||||
| 		else if (_options.kind == VarDeclKind::State && token == Token::Override) | ||||
| 		{ | ||||
| 			if (overrides) | ||||
| 				parserError(9125_error, "Override already specified."); | ||||
| @ -1928,6 +1933,16 @@ pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::pars | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| bool Parser::variableDeclarationStart() | ||||
| { | ||||
| 	Token currentToken = m_scanner->currentToken(); | ||||
| 	return | ||||
| 		currentToken == Token::Identifier || | ||||
| 		currentToken == Token::Mapping || | ||||
| 		TokenTraits::isElementaryTypeName(currentToken) || | ||||
| 		(currentToken == Token::Function && m_scanner->peekNextToken() == Token::LParen); | ||||
| } | ||||
| 
 | ||||
| optional<string> Parser::findLicenseString(std::vector<ASTPointer<ASTNode>> const& _nodes) | ||||
| { | ||||
| 	// We circumvent the scanner here, because it skips non-docstring comments.
 | ||||
|  | ||||
| @ -52,13 +52,13 @@ public: | ||||
| private: | ||||
| 	class ASTNodeFactory; | ||||
| 
 | ||||
| 	enum class VarDeclKind { FileLevel, State, Other }; | ||||
| 	struct VarDeclParserOptions | ||||
| 	{ | ||||
| 		// This is actually not needed, but due to a defect in the C++ standard, we have to.
 | ||||
| 		// https://stackoverflow.com/questions/17430377
 | ||||
| 		VarDeclParserOptions() {} | ||||
| 
 | ||||
| 		bool isStateVariable = false; | ||||
| 		VarDeclKind kind = VarDeclKind::Other; | ||||
| 		bool allowIndexed = false; | ||||
| 		bool allowEmptyName = false; | ||||
| 		bool allowInitialValue = false; | ||||
| @ -155,6 +155,9 @@ private: | ||||
| 	///@{
 | ||||
| 	///@name Helper functions
 | ||||
| 
 | ||||
| 	/// @return true if we are at the start of a variable declaration.
 | ||||
| 	bool variableDeclarationStart(); | ||||
| 
 | ||||
| 	/// Used as return value of @see peekStatementType.
 | ||||
| 	enum class LookAheadInfo | ||||
| 	{ | ||||
|  | ||||
| @ -66,14 +66,13 @@ struct MemoryOffsetAllocator | ||||
| 		if (unreachableVariables.count(_function)) | ||||
| 		{ | ||||
| 			yulAssert(!slotAllocations.count(_function), ""); | ||||
| 			auto& assignedSlots = slotAllocations[_function]; | ||||
| 			for (YulString variable: unreachableVariables.at(_function)) | ||||
| 				if (variable.empty()) | ||||
| 				{ | ||||
| 					// TODO: Too many function arguments or return parameters.
 | ||||
| 				} | ||||
| 				else | ||||
| 					assignedSlots[variable] = requiredSlots++; | ||||
| 					slotAllocations[variable] = requiredSlots++; | ||||
| 		} | ||||
| 
 | ||||
| 		return slotsRequiredForFunction[_function] = requiredSlots; | ||||
| @ -82,7 +81,7 @@ struct MemoryOffsetAllocator | ||||
| 	map<YulString, set<YulString>> const& unreachableVariables; | ||||
| 	map<YulString, set<YulString>> const& callGraph; | ||||
| 
 | ||||
| 	map<YulString, map<YulString, uint64_t>> slotAllocations{}; | ||||
| 	map<YulString, uint64_t> slotAllocations{}; | ||||
| 	map<YulString, uint64_t> slotsRequiredForFunction{}; | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -54,25 +54,22 @@ vector<Statement> generateMemoryStore( | ||||
| void StackToMemoryMover::run( | ||||
| 	OptimiserStepContext& _context, | ||||
| 	u256 _reservedMemory, | ||||
| 	map<YulString, map<YulString, uint64_t>> const& _memorySlots, | ||||
| 	map<YulString, uint64_t> const& _memorySlots, | ||||
| 	uint64_t _numRequiredSlots, | ||||
| 	Block& _block | ||||
| ) | ||||
| { | ||||
| 	StackToMemoryMover stackToMemoryMover(_context, _reservedMemory, _memorySlots, _numRequiredSlots); | ||||
| 	VariableMemoryOffsetTracker memoryOffsetTracker(_reservedMemory, _memorySlots, _numRequiredSlots); | ||||
| 	StackToMemoryMover stackToMemoryMover(_context, memoryOffsetTracker); | ||||
| 	stackToMemoryMover(_block); | ||||
| } | ||||
| 
 | ||||
| StackToMemoryMover::StackToMemoryMover( | ||||
| 	OptimiserStepContext& _context, | ||||
| 	u256 _reservedMemory, | ||||
| 	map<YulString, map<YulString, uint64_t>> const& _memorySlots, | ||||
| 	uint64_t _numRequiredSlots | ||||
| 	VariableMemoryOffsetTracker const& _memoryOffsetTracker | ||||
| ): | ||||
| m_context(_context), | ||||
| m_reservedMemory(std::move(_reservedMemory)), | ||||
| m_memorySlots(_memorySlots), | ||||
| m_numRequiredSlots(_numRequiredSlots), | ||||
| m_memoryOffsetTracker(_memoryOffsetTracker), | ||||
| m_nameDispenser(_context.dispenser) | ||||
| { | ||||
| 	auto const* evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect); | ||||
| @ -80,41 +77,25 @@ m_nameDispenser(_context.dispenser) | ||||
| 		evmDialect && evmDialect->providesObjectAccess(), | ||||
| 		"StackToMemoryMover can only be run on objects using the EVMDialect with object access." | ||||
| 	); | ||||
| 
 | ||||
| 	if (m_memorySlots.count(YulString{})) | ||||
| 		// If the global scope contains variables to be moved, start with those as if it were a function.
 | ||||
| 		m_currentFunctionMemorySlots = &m_memorySlots.at(YulString{}); | ||||
| } | ||||
| 
 | ||||
| void StackToMemoryMover::operator()(FunctionDefinition& _functionDefinition) | ||||
| { | ||||
| 	if (m_memorySlots.count(_functionDefinition.name)) | ||||
| 	{ | ||||
| 		map<YulString, uint64_t> const* saved = m_currentFunctionMemorySlots; | ||||
| 		m_currentFunctionMemorySlots = &m_memorySlots.at(_functionDefinition.name); | ||||
| 		for (TypedName const& param: _functionDefinition.parameters + _functionDefinition.returnVariables) | ||||
| 			if (m_currentFunctionMemorySlots->count(param.name)) | ||||
| 			{ | ||||
| 				// TODO: we cannot handle function parameters yet.
 | ||||
| 				m_currentFunctionMemorySlots = saved; | ||||
| 				return; | ||||
| 			} | ||||
| 		ASTModifier::operator()(_functionDefinition); | ||||
| 		m_currentFunctionMemorySlots = saved; | ||||
| 	} | ||||
| 	for (TypedName const& param: _functionDefinition.parameters + _functionDefinition.returnVariables) | ||||
| 		if (m_memoryOffsetTracker(param.name)) | ||||
| 		{ | ||||
| 			// TODO: we cannot handle function parameters yet.
 | ||||
| 			return; | ||||
| 		} | ||||
| 	ASTModifier::operator()(_functionDefinition); | ||||
| } | ||||
| 
 | ||||
| void StackToMemoryMover::operator()(Block& _block) | ||||
| { | ||||
| 	using OptionalStatements = std::optional<vector<Statement>>; | ||||
| 	if (!m_currentFunctionMemorySlots) | ||||
| 	{ | ||||
| 		ASTModifier::operator()(_block); | ||||
| 		return; | ||||
| 	} | ||||
| 	auto containsVariableNeedingEscalation = [&](auto const& _variables) { | ||||
| 		return util::contains_if(_variables, [&](auto const& var) { | ||||
| 			return m_currentFunctionMemorySlots->count(var.name); | ||||
| 			return m_memoryOffsetTracker(var.name); | ||||
| 		}); | ||||
| 	}; | ||||
| 	auto rewriteAssignmentOrVariableDeclaration = [&]( | ||||
| @ -123,12 +104,16 @@ void StackToMemoryMover::operator()(Block& _block) | ||||
| 		std::unique_ptr<Expression> _value | ||||
| 	) -> std::vector<Statement> { | ||||
| 		if (_variables.size() == 1) | ||||
| 		{ | ||||
| 			optional<YulString> offset = m_memoryOffsetTracker(_variables.front().name); | ||||
| 			yulAssert(offset, ""); | ||||
| 			return generateMemoryStore( | ||||
| 				m_context.dialect, | ||||
| 				_loc, | ||||
| 				memoryOffset(_variables.front().name), | ||||
| 				*offset, | ||||
| 				_value ? *std::move(_value) : Literal{_loc, LiteralKind::Number, "0"_yulstring, {}} | ||||
| 			); | ||||
| 		} | ||||
| 
 | ||||
| 		VariableDeclaration tempDecl{_loc, {}, std::move(_value)}; | ||||
| 		vector<Statement> memoryAssignments; | ||||
| @ -138,11 +123,11 @@ void StackToMemoryMover::operator()(Block& _block) | ||||
| 			YulString tempVarName = m_nameDispenser.newName(var.name); | ||||
| 			tempDecl.variables.emplace_back(TypedName{var.location, tempVarName, {}}); | ||||
| 
 | ||||
| 			if (m_currentFunctionMemorySlots->count(var.name)) | ||||
| 			if (optional<YulString> offset = m_memoryOffsetTracker(var.name)) | ||||
| 				memoryAssignments += generateMemoryStore( | ||||
| 					m_context.dialect, | ||||
| 					_loc, | ||||
| 					memoryOffset(var.name), | ||||
| 					*offset, | ||||
| 					Identifier{_loc, tempVarName} | ||||
| 				); | ||||
| 			else if constexpr (std::is_same_v<std::decay_t<decltype(var)>, Identifier>) | ||||
| @ -201,35 +186,36 @@ void StackToMemoryMover::operator()(Block& _block) | ||||
| 
 | ||||
| void StackToMemoryMover::visit(Expression& _expression) | ||||
| { | ||||
| 	if ( | ||||
| 		Identifier* identifier = std::get_if<Identifier>(&_expression); | ||||
| 		identifier && m_currentFunctionMemorySlots && m_currentFunctionMemorySlots->count(identifier->name) | ||||
| 	) | ||||
| 	{ | ||||
| 		BuiltinFunction const* memoryLoadFunction = m_context.dialect.memoryLoadFunction(m_context.dialect.defaultType); | ||||
| 		yulAssert(memoryLoadFunction, ""); | ||||
| 		langutil::SourceLocation loc = identifier->location; | ||||
| 		_expression = FunctionCall{ | ||||
| 			loc, | ||||
| 			Identifier{loc, memoryLoadFunction->name}, { | ||||
| 				Literal{ | ||||
| 					loc, | ||||
| 					LiteralKind::Number, | ||||
| 					memoryOffset(identifier->name), | ||||
| 					{} | ||||
| 	if (Identifier* identifier = std::get_if<Identifier>(&_expression)) | ||||
| 		if (optional<YulString> offset = m_memoryOffsetTracker(identifier->name)) | ||||
| 		{ | ||||
| 			BuiltinFunction const* memoryLoadFunction = m_context.dialect.memoryLoadFunction(m_context.dialect.defaultType); | ||||
| 			yulAssert(memoryLoadFunction, ""); | ||||
| 			langutil::SourceLocation loc = identifier->location; | ||||
| 			_expression = FunctionCall{ | ||||
| 				loc, | ||||
| 				Identifier{loc, memoryLoadFunction->name}, { | ||||
| 					Literal{ | ||||
| 						loc, | ||||
| 						LiteralKind::Number, | ||||
| 						*offset, | ||||
| 						{} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}; | ||||
| 			}; | ||||
| 			return; | ||||
| 		} | ||||
| 	ASTModifier::visit(_expression); | ||||
| } | ||||
| 
 | ||||
| optional<YulString> StackToMemoryMover::VariableMemoryOffsetTracker::operator()(YulString _variable) const | ||||
| { | ||||
| 	if (m_memorySlots.count(_variable)) | ||||
| 	{ | ||||
| 		uint64_t slot = m_memorySlots.at(_variable); | ||||
| 		yulAssert(slot < m_numRequiredSlots, ""); | ||||
| 		return YulString{util::toCompactHexWithPrefix(m_reservedMemory + 32 * (m_numRequiredSlots - slot - 1))}; | ||||
| 	} | ||||
| 	else | ||||
| 		ASTModifier::visit(_expression); | ||||
| 		return std::nullopt; | ||||
| } | ||||
| 
 | ||||
| YulString StackToMemoryMover::memoryOffset(YulString _variable) | ||||
| { | ||||
| 	yulAssert(m_currentFunctionMemorySlots, ""); | ||||
| 	uint64_t slot = m_currentFunctionMemorySlots->at(_variable); | ||||
| 	yulAssert(slot < m_numRequiredSlots, ""); | ||||
| 	return YulString{util::toCompactHexWithPrefix(m_reservedMemory + 32 * (m_numRequiredSlots - slot - 1))}; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -90,7 +90,7 @@ public: | ||||
| 	static void run( | ||||
| 		OptimiserStepContext& _context, | ||||
| 		u256 _reservedMemory, | ||||
| 		std::map<YulString, std::map<YulString, uint64_t>> const& _memorySlots, | ||||
| 		std::map<YulString, uint64_t> const& _memorySlots, | ||||
| 		uint64_t _numRequiredSlots, | ||||
| 		Block& _block | ||||
| 	); | ||||
| @ -100,22 +100,33 @@ public: | ||||
| 	void operator()(Block& _block) override; | ||||
| 	void visit(Expression& _expression) override; | ||||
| private: | ||||
| 	class VariableMemoryOffsetTracker | ||||
| 	{ | ||||
| 	public: | ||||
| 		VariableMemoryOffsetTracker( | ||||
| 			u256 _reservedMemory, | ||||
| 			std::map<YulString, uint64_t> const& _memorySlots, | ||||
| 			uint64_t _numRequiredSlots | ||||
| 		): m_reservedMemory(_reservedMemory), m_memorySlots(_memorySlots), m_numRequiredSlots(_numRequiredSlots) | ||||
| 		{} | ||||
| 
 | ||||
| 		/// @returns a YulString containing the memory offset to be assigned to @a _variable as number literal
 | ||||
| 		/// or std::nullopt if the variable should not be moved.
 | ||||
| 		std::optional<YulString> operator()(YulString _variable) const; | ||||
| 	private: | ||||
| 		u256 m_reservedMemory; | ||||
| 		std::map<YulString, uint64_t> const& m_memorySlots; | ||||
| 		uint64_t m_numRequiredSlots = 0; | ||||
| 	}; | ||||
| 
 | ||||
| 	StackToMemoryMover( | ||||
| 		OptimiserStepContext& _context, | ||||
| 		u256 _reservedMemory, | ||||
| 		std::map<YulString, std::map<YulString, uint64_t>> const& _memorySlots, | ||||
| 		uint64_t _numRequiredSlots | ||||
| 		VariableMemoryOffsetTracker const& _memoryOffsetTracker | ||||
| 	); | ||||
| 
 | ||||
| 	/// @returns a YulString containing the memory offset to be assigned to @a _variable as number literal.
 | ||||
| 	YulString memoryOffset(YulString _variable); | ||||
| 
 | ||||
| 	OptimiserStepContext& m_context; | ||||
| 	u256 m_reservedMemory; | ||||
| 	std::map<YulString, std::map<YulString, uint64_t>> const& m_memorySlots; | ||||
| 	uint64_t m_numRequiredSlots = 0; | ||||
| 	VariableMemoryOffsetTracker const& m_memoryOffsetTracker; | ||||
| 	NameDispenser& m_nameDispenser; | ||||
| 	std::map<YulString, uint64_t> const* m_currentFunctionMemorySlots = nullptr; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| Error: Expected pragma, import directive or contract/interface/library/struct/enum/function definition. | ||||
| Error: Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. | ||||
|  --> recovery_ast_empty_contract/input.sol:3:1: | ||||
|   | | ||||
| 3 | c | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| Error (1834): Unimplemented feature error: setToZero for type t_struct$_S_$4_storage not yet implemented! in FILENAME REMOVED | ||||
|  --> yul_unimplemented/input.sol:9:9: | ||||
| Error (1834): Unimplemented feature error: Byte Arrays not yet implemented! in FILENAME REMOVED | ||||
|  --> yul_unimplemented/input.sol:6:9: | ||||
|   | | ||||
| 9 |         delete str; | ||||
|   |         ^^^^^^^^^^ | ||||
| 6 |         delete a; | ||||
|   |         ^^^^^^^^ | ||||
|  | ||||
| @ -1,11 +1,8 @@ | ||||
| // SPDX-License-Identifier: GPL-3.0 | ||||
| pragma solidity >=0.0; | ||||
| contract test { | ||||
|     struct S { | ||||
|         uint x; | ||||
|     } | ||||
|     S str; | ||||
|     constructor() { | ||||
|         delete str; | ||||
|     bytes a; | ||||
|     function f() public { | ||||
|         delete a; | ||||
|     } | ||||
| } | ||||
| @ -1,4 +1,6 @@ | ||||
| pragma solidity ^99.99.0; | ||||
| this is surely invalid | ||||
| // ---- | ||||
| // ParserError 7858: (26-30): Expected pragma, import directive or contract/interface/library/struct/enum/function definition. | ||||
| // ParserError 6635: (31-33): Expected identifier but got 'is' | ||||
| // ParserError 6635: (34-40): Expected ';' but got identifier | ||||
| // ParserError 6635: (49-49): Expected ';' but got end of source | ||||
|  | ||||
| @ -16,5 +16,7 @@ contract c { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // test() -> 0x20, 0x8, -1, -1, 8, -16, -2, 6, 8, -1 | ||||
|  | ||||
| @ -6,5 +6,7 @@ contract C { | ||||
|         return (b[0], b.length); | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 1, 3 | ||||
|  | ||||
| @ -8,5 +8,7 @@ contract c { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // test() -> 0x20, 29, 0x0303030303030303030303030303030303030303030303030303030303000000 | ||||
|  | ||||
| @ -8,5 +8,7 @@ contract c { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // test() -> 0x20, 33, 0x303030303030303030303030303030303030303030303030303030303030303, 0x0300000000000000000000000000000000000000000000000000000000000000 | ||||
|  | ||||
| @ -0,0 +1,21 @@ | ||||
| contract C { | ||||
|     uint72[5][] a; | ||||
| 
 | ||||
|     function f() public returns (uint72, uint72, uint72, uint72, uint72, uint72, uint72) { | ||||
|         for (uint i = 0; i < 4; i++) | ||||
|             a.push(); | ||||
|         a[0][0] = 1; | ||||
|         a[0][3] = 2; | ||||
|         a[1][1] = 3; | ||||
|         a[1][4] = 4; | ||||
|         a[2][0] = 5; | ||||
|         a[3][2] = 6; | ||||
|         a[3][3] = 7; | ||||
|         uint72[5][] memory m = a; | ||||
|         return (m[0][0], m[0][3], m[1][1], m[1][4], m[2][0], m[3][2], m[3][3]); | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 1, 2, 3, 4, 5, 6, 7 | ||||
| @ -0,0 +1,13 @@ | ||||
| pragma experimental ABIEncoderV2; | ||||
| contract C { | ||||
|     bytes[] a; | ||||
| 
 | ||||
|     function f() public returns (bytes[] memory) { | ||||
|         a.push("abc"); | ||||
|         a.push("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVXYZ"); | ||||
|         bytes[] memory m = a; | ||||
|         return m; | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // f() -> 0x20, 0x02, 0x40, 0x80, 3, 0x6162630000000000000000000000000000000000000000000000000000000000, 0x99, 44048183304486788312148433451363384677562265908331949128489393215789685032262, 32241931068525137014058842823026578386641954854143559838526554899205067598957, 49951309422467613961193228765530489307475214998374779756599339590522149884499, 0x54555658595a6162636465666768696a6b6c6d6e6f707172737475767778797a, 0x4142434445464748494a4b4c4d4e4f5051525354555658595a00000000000000 | ||||
| @ -0,0 +1,22 @@ | ||||
| contract C { | ||||
|     uint72[5][] a; | ||||
| 
 | ||||
|     function f() public returns (uint72, uint72, uint72, uint72, uint72, uint72, uint72) { | ||||
|         for (uint i = 0; i < 4; i++) | ||||
|             a.push(); | ||||
|         a[0][0] = 1; | ||||
|         a[0][3] = 2; | ||||
|         a[1][1] = 3; | ||||
|         a[1][4] = 4; | ||||
|         a[2][0] = 5; | ||||
|         a[3][2] = 6; | ||||
|         a[3][3] = 7; | ||||
|         uint72[5][] storage a_ = a; | ||||
|         uint72[5][] memory m = a_; | ||||
|         return (m[0][0], m[0][3], m[1][1], m[1][4], m[2][0], m[3][2], m[3][3]); | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 1, 2, 3, 4, 5, 6, 7 | ||||
| @ -0,0 +1,26 @@ | ||||
| contract C { | ||||
|     struct T { uint8 x; uint8 y; uint[] z; } | ||||
|     T[3][] a; | ||||
| 
 | ||||
|     function f() public returns (uint8, uint8, uint, uint, uint, uint8, uint8, uint, uint, uint) { | ||||
|         a.push(); | ||||
|         a.push(); | ||||
|         a[0][1].x = 11; | ||||
|         a[0][1].y = 12; | ||||
|         a[0][1].z.push(1); | ||||
|         a[0][1].z.push(2); | ||||
|         a[0][1].z.push(3); | ||||
|         a[1][2].x = 21; | ||||
|         a[1][2].y = 22; | ||||
|         a[1][2].z.push(4); | ||||
|         a[1][2].z.push(5); | ||||
|         a[1][2].z.push(6); | ||||
|         T[3][] memory m = a; | ||||
|         return ( | ||||
|             m[0][1].x, m[0][1].y, m[0][1].z[0], m[0][1].z[1], m[0][1].z[2], | ||||
|             m[1][2].x, m[1][2].y, m[1][2].z[0], m[1][2].z[1], m[1][2].z[2] | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // f() -> 11, 0x0c, 1, 2, 3, 0x15, 22, 4, 5, 6 | ||||
| @ -0,0 +1,15 @@ | ||||
| contract C { | ||||
|     uint8[33] a; | ||||
| 
 | ||||
|     function f() public returns (uint8, uint8, uint8) { | ||||
|         a[0] = 2; | ||||
|         a[16] = 3; | ||||
|         a[32] = 4; | ||||
|         uint8[33] memory m = a; | ||||
|         return (m[0], m[16], m[32]); | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 2, 3, 4 | ||||
| @ -0,0 +1,17 @@ | ||||
| contract C { | ||||
|     uint8[] a; | ||||
| 
 | ||||
|     function f() public returns (uint8, uint8, uint8) { | ||||
|         for (uint i = 0; i < 33; i++) | ||||
|             a.push(7); | ||||
|         a[0] = 2; | ||||
|         a[16] = 3; | ||||
|         a[32] = 4; | ||||
|         uint8[] memory m = a; | ||||
|         return (m[0], m[16], m[32]); | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 2, 3, 4 | ||||
| @ -16,5 +16,7 @@ contract C { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> true # This code interprets x as an array length and thus will go out of gas. neither of the two should throw due to out-of-bounds access # | ||||
|  | ||||
| @ -15,5 +15,7 @@ contract Test { | ||||
|         return set(data)[1]; | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 0x20, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x07 | ||||
|  | ||||
| @ -31,6 +31,8 @@ contract C { | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // l() -> 0 | ||||
| // f(uint256): 42 -> | ||||
|  | ||||
| @ -8,5 +8,7 @@ contract c { | ||||
|         return keccak256(abi.encodePacked("b", keccak256(data), "a")); | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // foo() -> 0xb338eefce206f9f57b83aa738deecd5326dc4b72dd81ee6a7c621a6facb7acdc | ||||
|  | ||||
| @ -9,5 +9,7 @@ contract c { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // foo() -> true | ||||
|  | ||||
| @ -0,0 +1,9 @@ | ||||
| address constant e = 0x1212121212121212121212121000002134593163; | ||||
| 
 | ||||
| contract C { | ||||
|   function f() public returns (address z) { | ||||
|     assembly { z := e } | ||||
|   } | ||||
| } | ||||
| // ---- | ||||
| // f() -> 0x1212121212121212121212121000002134593163 | ||||
| @ -0,0 +1,33 @@ | ||||
| bytes constant a = "\x03\x01\x02"; | ||||
| bytes constant b = hex"030102"; | ||||
| string constant c = "hello"; | ||||
| uint256 constant x = 56; | ||||
| enum ActionChoices {GoLeft, GoRight, GoStraight, Sit} | ||||
| ActionChoices constant choices = ActionChoices.GoRight; | ||||
| bytes32 constant st = "abc\x00\xff__"; | ||||
| 
 | ||||
| contract C { | ||||
|     function f() public returns (bytes memory) { | ||||
|         return a; | ||||
|     } | ||||
| 
 | ||||
|     function g() public returns (bytes memory) { | ||||
|         return b; | ||||
|     } | ||||
| 
 | ||||
|     function h() public returns (bytes memory) { | ||||
|         return bytes(c); | ||||
|     } | ||||
| 
 | ||||
|     function i() public returns (uint, ActionChoices, bytes32) { | ||||
|         return (x, choices, st); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 0x20, 3, "\x03\x01\x02" | ||||
| // g() -> 0x20, 3, "\x03\x01\x02" | ||||
| // h() -> 0x20, 5, "hello" | ||||
| // i() -> 0x38, 1, 0x61626300ff5f5f00000000000000000000000000000000000000000000000000 | ||||
| @ -0,0 +1,41 @@ | ||||
| ==== Source: s1.sol ==== | ||||
| 
 | ||||
| 
 | ||||
| bytes constant a = b; | ||||
| bytes constant b = hex"030102"; | ||||
| 
 | ||||
| function fre() pure returns (bytes memory) { | ||||
|     return a; | ||||
| } | ||||
| 
 | ||||
| ==== Source: s2.sol ==== | ||||
| 
 | ||||
| import "s1.sol"; | ||||
| 
 | ||||
| uint256 constant c = uint8(a[0]) + 2; | ||||
| 
 | ||||
| contract C { | ||||
|     function f() public returns (bytes memory) { | ||||
|         return a; | ||||
|     } | ||||
| 
 | ||||
|     function g() public returns (bytes memory) { | ||||
|         return b; | ||||
|     } | ||||
| 
 | ||||
|     function h() public returns (uint) { | ||||
|         return c; | ||||
|     } | ||||
| 
 | ||||
|     function i() public returns (bytes memory) { | ||||
|         return fre(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 0x20, 3, "\x03\x01\x02" | ||||
| // g() -> 0x20, 3, "\x03\x01\x02" | ||||
| // h() -> 5 | ||||
| // i() -> 0x20, 3, "\x03\x01\x02" | ||||
| @ -0,0 +1,26 @@ | ||||
| ==== Source: s1.sol ==== | ||||
| 
 | ||||
| 
 | ||||
| uint constant a = 89; | ||||
| 
 | ||||
| function fre() pure returns (uint) { | ||||
|     return a; | ||||
| } | ||||
| 
 | ||||
| ==== Source: s2.sol ==== | ||||
| 
 | ||||
| import {a as b, fre} from "s1.sol"; | ||||
| import "s1.sol" as M; | ||||
| 
 | ||||
| uint256 constant a = 13; | ||||
| 
 | ||||
| contract C { | ||||
|     function f() public returns (uint, uint, uint, uint) { | ||||
|         return (a, fre(), M.a, b); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 0x0d, 0x59, 0x59, 0x59 | ||||
| @ -23,6 +23,8 @@ contract test { | ||||
|         return ret; | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f(bool): true -> 1 | ||||
| // f(bool): false -> 2 | ||||
|  | ||||
| @ -0,0 +1,15 @@ | ||||
| ==== Source: a.sol ==== | ||||
| function f(uint) pure returns (uint) { return 7; } | ||||
| function f(bytes memory x) pure returns (uint) { return x.length; } | ||||
| ==== Source: b.sol ==== | ||||
| import "a.sol" as M; | ||||
| contract C { | ||||
|     function f() public pure returns (uint, uint) { | ||||
|         return (M.f(2), M.f("abc")); | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 7, 3 | ||||
| @ -18,6 +18,8 @@ contract C { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> true | ||||
| // a() -> 7 | ||||
|  | ||||
| @ -21,5 +21,7 @@ contract C { | ||||
|     return (s.f(), h(s)); | ||||
|   } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // g() -> 7, 7 | ||||
|  | ||||
| @ -0,0 +1,26 @@ | ||||
| contract C { | ||||
|     struct S { | ||||
|         uint16 v; | ||||
|         S[] x; | ||||
|     } | ||||
|     uint8[77] padding; | ||||
|     S s; | ||||
|     constructor() { | ||||
|          s.v = 21; | ||||
|          s.x.push(); s.x.push(); s.x.push(); | ||||
|          s.x[0].v = 101; s.x[1].v = 102; s.x[2].v = 103; | ||||
|     } | ||||
|     function f() public returns (uint256 a, uint256 b, uint256 c, uint256 d) { | ||||
|        S storage sptr1 = s.x[0]; | ||||
|        S storage sptr2 = s.x[1]; | ||||
|        S storage sptr3 = s.x[2]; | ||||
|        uint256 slot1; uint256 slot2; uint256 slot3; | ||||
|        assembly { slot1 := sptr1.slot slot2 := sptr2.slot slot3 := sptr3.slot } | ||||
|        delete s; | ||||
|        assembly { a := sload(s.slot) b := sload(slot1) c := sload(slot2) d := sload(slot3) } | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 0, 0, 0, 0 | ||||
| @ -15,5 +15,7 @@ contract C { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> 1 | ||||
|  | ||||
| @ -16,5 +16,7 @@ contract test { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // deleteMember() -> 0 | ||||
|  | ||||
| @ -0,0 +1,23 @@ | ||||
| contract C { | ||||
|     struct S { | ||||
|         uint256 x; | ||||
|         uint128 y; | ||||
|         uint32 z; | ||||
|     } | ||||
|     uint8 b = 23; | ||||
|     S s; | ||||
|     uint8 a = 17; | ||||
|     function f() public { | ||||
|         s.x = 42; s.y = 42; s.y = 42; | ||||
|         delete s; | ||||
|         assert(s.x == 0); | ||||
|         assert(s.y == 0); | ||||
|         assert(s.z == 0); | ||||
|         assert(b == 23); | ||||
|         assert(a == 17); | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> | ||||
| @ -0,0 +1,47 @@ | ||||
| pragma experimental ABIEncoderV2; | ||||
| 
 | ||||
| contract C { | ||||
|     struct S { | ||||
|         uint128 a; | ||||
|         uint256[] x; | ||||
|         uint240 b; | ||||
|     } | ||||
|     uint8 b = 23; | ||||
|     S s; | ||||
|     uint8 a = 17; | ||||
|     function f() public { | ||||
|         delete s; | ||||
|         s.x.push(42); s.x.push(42); s.x.push(42); | ||||
|         delete s; | ||||
|         assert(s.x.length == 0); | ||||
|         uint256[] storage x = s.x; | ||||
|         assembly { sstore(x.slot, 3) } | ||||
|         assert(s.x[0] == 0); | ||||
|         assert(s.x[1] == 0); | ||||
|         assert(s.x[2] == 0); | ||||
|         assert(b == 23); | ||||
|         assert(a == 17); | ||||
|     } | ||||
| 
 | ||||
|     function g() public { | ||||
|         delete s; | ||||
|         s.x.push(42); s.x.push(42); s.x.push(42); | ||||
|         s.a = 1; s.b = 2; | ||||
|         delete s.x; | ||||
|         assert(s.x.length == 0); | ||||
|         uint256[] storage x = s.x; | ||||
|         assembly { sstore(x.slot, 3) } | ||||
|         assert(s.x[0] == 0); | ||||
|         assert(s.x[1] == 0); | ||||
|         assert(s.x[2] == 0); | ||||
|         assert(b == 23); | ||||
|         assert(a == 17); | ||||
|         assert(s.a == 1); | ||||
|         assert(s.b == 2); | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> | ||||
| // g() -> | ||||
| @ -14,5 +14,7 @@ contract test { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // deleteIt() -> 0 | ||||
|  | ||||
| @ -18,6 +18,8 @@ contract test { | ||||
|         inner.recursive[0].z = inner.recursive[1].z + 1; | ||||
|     } | ||||
| } | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // check() -> false | ||||
| // set() -> | ||||
|  | ||||
| @ -22,5 +22,7 @@ contract C { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ==== | ||||
| // compileViaYul: also | ||||
| // ---- | ||||
| // f() -> true | ||||
|  | ||||
| @ -4,6 +4,6 @@ contract C { | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // DeclarationError 1788: (28-45): The "constant" keyword can only be used for state variables. | ||||
| // DeclarationError 1788: (28-45): The "constant" keyword can only be used for state variables or variables at file level. | ||||
| // TypeError 5462: (69-72): Invalid array length, expected integer literal or constant expression. | ||||
| // TypeError 6651: (64-75): Data location must be "storage", "memory" or "calldata" for variable, but none was given. | ||||
|  | ||||
| @ -0,0 +1,3 @@ | ||||
| /// @dev Documentation | ||||
| uint constant x = 8; | ||||
| // ---- | ||||
| @ -0,0 +1,4 @@ | ||||
| /// Documentation | ||||
| uint constant x = 8; | ||||
| // ---- | ||||
| // DocstringParsingError 6546: (0-18): Documentation tag @notice not valid for file-level variables. | ||||
| @ -0,0 +1,3 @@ | ||||
| uint constant override x = 2; | ||||
| // ---- | ||||
| // ParserError 2314: (14-22): Expected identifier but got 'override' | ||||
| @ -0,0 +1,3 @@ | ||||
| uint constant x; | ||||
| // ---- | ||||
| // TypeError 4266: (0-15): Uninitialized "constant" variable. | ||||
| @ -0,0 +1,3 @@ | ||||
| uint constant virtual x; | ||||
| // ---- | ||||
| // ParserError 2314: (14-21): Expected identifier but got 'virtual' | ||||
| @ -0,0 +1,3 @@ | ||||
| uint public constant x = 7; | ||||
| // ---- | ||||
| // ParserError 2314: (5-11): Expected identifier but got 'public' | ||||
| @ -0,0 +1,3 @@ | ||||
| uint constant public y = 7; | ||||
| // ---- | ||||
| // ParserError 2314: (14-20): Expected identifier but got 'public' | ||||
							
								
								
									
										15
									
								
								test/libsolidity/syntaxTests/constants/cross_file_cyclic.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								test/libsolidity/syntaxTests/constants/cross_file_cyclic.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| ==== Source: a ==== | ||||
| import "b"; | ||||
| uint constant c = d; | ||||
| ==== Source: b ==== | ||||
| import "a"; | ||||
| uint constant b = c; | ||||
| uint constant d = b; | ||||
| contract C { | ||||
|     uint constant a = b; | ||||
| } | ||||
| // ---- | ||||
| // TypeError 6161: (b:12-31): The value of the constant b has a cyclic dependency via c. | ||||
| // TypeError 6161: (b:33-52): The value of the constant d has a cyclic dependency via b. | ||||
| // TypeError 6161: (b:71-90): The value of the constant a has a cyclic dependency via b. | ||||
| // TypeError 6161: (a:12-31): The value of the constant c has a cyclic dependency via d. | ||||
| @ -0,0 +1,15 @@ | ||||
| ==== Source: a ==== | ||||
| import "b"; | ||||
| uint constant c = d; | ||||
| ==== Source: b ==== | ||||
| import "a" as M; | ||||
| uint constant b = M.c; | ||||
| uint constant d = b; | ||||
| contract C { | ||||
|     uint constant a = b; | ||||
| } | ||||
| // ---- | ||||
| // TypeError 6161: (b:17-38): The value of the constant b has a cyclic dependency via c. | ||||
| // TypeError 6161: (b:40-59): The value of the constant d has a cyclic dependency via b. | ||||
| // TypeError 6161: (b:78-97): The value of the constant a has a cyclic dependency via b. | ||||
| // TypeError 6161: (a:12-31): The value of the constant c has a cyclic dependency via d. | ||||
| @ -0,0 +1,3 @@ | ||||
| uint[] memory constant x = 2; | ||||
| // ---- | ||||
| // ParserError 2314: (7-13): Expected identifier but got 'memory' | ||||
| @ -0,0 +1,3 @@ | ||||
| uint[] constant memory x = 2; | ||||
| // ---- | ||||
| // ParserError 2314: (16-22): Expected identifier but got 'memory' | ||||
| @ -0,0 +1,4 @@ | ||||
| uint immutable x = 7; | ||||
| // ---- | ||||
| // DeclarationError 8342: (0-20): Only constant variables are allowed at file level. | ||||
| // DeclarationError 8297: (0-20): The "immutable" keyword can only be used for state variables. | ||||
| @ -0,0 +1,3 @@ | ||||
| mapping(uint => uint) constant b = b; | ||||
| // ---- | ||||
| // TypeError 9259: (0-36): Constants of non-value type not yet implemented. | ||||
| @ -0,0 +1,7 @@ | ||||
| ==== Source: a ==== | ||||
| uint constant c = 7; | ||||
| ==== Source: b ==== | ||||
| import {c as d} from "a"; | ||||
| uint constant d = 7; | ||||
| // ---- | ||||
| // DeclarationError 2333: (b:26-45): Identifier already declared. | ||||
							
								
								
									
										3
									
								
								test/libsolidity/syntaxTests/constants/non_constant.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/libsolidity/syntaxTests/constants/non_constant.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| uint x = 7; | ||||
| // ---- | ||||
| // DeclarationError 8342: (0-10): Only constant variables are allowed at file level. | ||||
| @ -0,0 +1,8 @@ | ||||
| ==== Source: a ==== | ||||
| import "b"; | ||||
| uint constant c = 7; | ||||
| ==== Source: b ==== | ||||
| import "a"; | ||||
| uint constant c = 7; | ||||
| // ---- | ||||
| // DeclarationError 2333: (b:12-31): Identifier already declared. | ||||
| @ -0,0 +1,4 @@ | ||||
| uint constant c = 7; | ||||
| uint constant c = 8; | ||||
| // ---- | ||||
| // DeclarationError 2333: (21-40): Identifier already declared. | ||||
| @ -0,0 +1,5 @@ | ||||
| struct S { uint x; } | ||||
| S constant s; | ||||
| // ---- | ||||
| // TypeError 9259: (21-33): Constants of non-value type not yet implemented. | ||||
| // TypeError 4266: (21-33): Uninitialized "constant" variable. | ||||
| @ -1,3 +1,3 @@ | ||||
| constructor() {} | ||||
| // ---- | ||||
| // ParserError 7858: (0-11): Expected pragma, import directive or contract/interface/library/struct/enum/function definition. | ||||
| // ParserError 7858: (0-11): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. | ||||
|  | ||||
| @ -1,3 +1,3 @@ | ||||
| fallback(){} | ||||
| // ---- | ||||
| // ParserError 7858: (0-8): Expected pragma, import directive or contract/interface/library/struct/enum/function definition. | ||||
| // ParserError 7858: (0-8): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. | ||||
|  | ||||
| @ -1,3 +1,3 @@ | ||||
| receive() {} | ||||
| // ---- | ||||
| // ParserError 7858: (0-7): Expected pragma, import directive or contract/interface/library/struct/enum/function definition. | ||||
| // ParserError 7858: (0-7): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. | ||||
|  | ||||
| @ -2,4 +2,4 @@ contract test { | ||||
|     function f(uint[] memory constant a) public { } | ||||
| } | ||||
| // ---- | ||||
| // DeclarationError 1788: (31-55): The "constant" keyword can only be used for state variables. | ||||
| // DeclarationError 1788: (31-55): The "constant" keyword can only be used for state variables or variables at file level. | ||||
|  | ||||
| @ -0,0 +1,4 @@ | ||||
| uint constant c = 7; | ||||
| function c() returns (uint) {} | ||||
| // ---- | ||||
| // DeclarationError 2333: (21-51): Identifier already declared. | ||||
| @ -11,4 +11,4 @@ contract C { | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // ParserError 2837: (290-295): Only state variables can have a docstring. | ||||
| // ParserError 2837: (290-295): Only state variables or file-level variables can have a docstring. | ||||
|  | ||||
| @ -2,4 +2,4 @@ contract Foo { | ||||
|     function f(uint[] storage constant x, uint[] memory y) internal { } | ||||
| } | ||||
| // ---- | ||||
| // DeclarationError 1788: (30-55): The "constant" keyword can only be used for state variables. | ||||
| // DeclarationError 1788: (30-55): The "constant" keyword can only be used for state variables or variables at file level. | ||||
|  | ||||
| @ -0,0 +1,6 @@ | ||||
| struct S { mapping(uint => uint)[2] a; } | ||||
| contract C { | ||||
|     function f(S storage s) public {} | ||||
| } | ||||
| // ---- | ||||
| // TypeError 6651: (69-80): Data location must be "memory" or "calldata" for parameter in function, but "storage" was given. | ||||
| @ -0,0 +1,4 @@ | ||||
| struct S { mapping(uint => uint)[2] a; } | ||||
| library L { | ||||
|     function f(S storage s) public {} | ||||
| } | ||||
| @ -0,0 +1,5 @@ | ||||
| library Test { | ||||
|     struct Nested { mapping(uint => uint)[2][] a; } | ||||
|     struct X { Nested n; } | ||||
|     function f(X storage x) public {} | ||||
| } | ||||
| @ -1,3 +1,3 @@ | ||||
| unexpected | ||||
| // ---- | ||||
| // ParserError 7858: (0-10): Expected pragma, import directive or contract/interface/library/struct/enum/function definition. | ||||
| // ParserError 7858: (0-10): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user