mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #1711 from ethereum/asmfunctions
Assembly: Analysis stage for functions.
This commit is contained in:
		
						commit
						2c1fb46bc3
					
				| @ -7,6 +7,8 @@ Features: | ||||
|  * Commandline interface: Add the ``--standard-json`` parameter to process a Standard JSON I/O. | ||||
|  * Commandline interface: Support ``--allow-paths`` to define trusted import paths. Note: the | ||||
|    path(s) of the supplied source file(s) is always trusted. | ||||
|  * Inline Assembly: Storage variable access using ``_slot`` and ``_offset`` suffixes. | ||||
|  * Inline Assembly: Disallow blocks with unbalanced stack. | ||||
|  * Static analyzer: Warn about statements without effects. | ||||
| 
 | ||||
| Bugfixes: | ||||
|  | ||||
| @ -323,9 +323,12 @@ Access to External Variables and Functions | ||||
| ------------------------------------------ | ||||
| 
 | ||||
| Solidity variables and other identifiers can be accessed by simply using their name. | ||||
| For storage and memory variables, this will push the address and not the value onto the | ||||
| stack. Also note that non-struct and non-array storage variable addresses occupy two slots | ||||
| on the stack: One for the address and one for the byte offset inside the storage slot. | ||||
| For memory variables, this will push the address and not the value onto the | ||||
| stack. Storage variables are different: Values in storage might not occupy a | ||||
| full storage slot, so their "address" is composed of a slot and a byte-offset | ||||
| inside that slot. To retrieve the slot pointed to by the variable ``x``, you | ||||
| used ``x_slot`` and to retrieve the byte-offset you used ``x_offset``. | ||||
| 
 | ||||
| In assignments (see below), we can even use local Solidity variables to assign to. | ||||
| 
 | ||||
| Functions external to inline assembly can also be accessed: The assembly will | ||||
| @ -340,17 +343,13 @@ changes during the call, and thus references to local variables will be wrong. | ||||
| 
 | ||||
| .. code:: | ||||
| 
 | ||||
|     pragma solidity ^0.4.0; | ||||
|     pragma solidity ^0.4.11; | ||||
| 
 | ||||
|     contract C { | ||||
|         uint b; | ||||
|         function f(uint x) returns (uint r) { | ||||
|             assembly { | ||||
|                 b pop // remove the offset, we know it is zero | ||||
|                 sload | ||||
|                 x | ||||
|                 mul | ||||
|                 =: r  // assign to return variable r | ||||
|                 r := mul(x, sload(b_slot)) // ignore the offset, we know it is zero | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -25,9 +25,12 @@ | ||||
| #include <libsolidity/analysis/NameAndTypeResolver.h> | ||||
| #include <libsolidity/interface/Exceptions.h> | ||||
| #include <libsolidity/analysis/ConstantEvaluator.h> | ||||
| #include <libsolidity/inlineasm/AsmCodeGen.h> | ||||
| #include <libsolidity/inlineasm/AsmAnalysis.h> | ||||
| #include <libsolidity/inlineasm/AsmAnalysisInfo.h> | ||||
| #include <libsolidity/inlineasm/AsmData.h> | ||||
| 
 | ||||
| #include <boost/algorithm/string.hpp> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace dev; | ||||
| using namespace dev::solidity; | ||||
| @ -158,21 +161,40 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) | ||||
| 
 | ||||
| bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) | ||||
| { | ||||
| 	// We need to perform a full code generation pass here as inline assembly does not distinguish
 | ||||
| 	// reference resolution and code generation.
 | ||||
| 	// Errors created in this stage are completely ignored because we do not yet know
 | ||||
| 	// the type and size of external identifiers, which would result in false errors.
 | ||||
| 	// The only purpose of this step is to fill the inline assembly annotation with
 | ||||
| 	// external references.
 | ||||
| 	ErrorList errorsIgnored; | ||||
| 	assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errorsIgnored); | ||||
| 	codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly&, assembly::CodeGenerator::IdentifierContext) { | ||||
| 	assembly::ExternalIdentifierAccess::Resolver resolver = | ||||
| 	[&](assembly::Identifier const& _identifier, assembly::IdentifierContext) { | ||||
| 		auto declarations = m_resolver.nameFromCurrentScope(_identifier.name); | ||||
| 		bool isSlot = boost::algorithm::ends_with(_identifier.name, "_slot"); | ||||
| 		bool isOffset = boost::algorithm::ends_with(_identifier.name, "_offset"); | ||||
| 		if (isSlot || isOffset) | ||||
| 		{ | ||||
| 			// special mode to access storage variables
 | ||||
| 			if (!declarations.empty()) | ||||
| 				// the special identifier exists itself, we should not allow that.
 | ||||
| 				return size_t(-1); | ||||
| 			string realName = _identifier.name.substr(0, _identifier.name.size() - ( | ||||
| 				isSlot ? | ||||
| 				string("_slot").size() : | ||||
| 				string("_offset").size() | ||||
| 			)); | ||||
| 			declarations = m_resolver.nameFromCurrentScope(realName); | ||||
| 		} | ||||
| 		if (declarations.size() != 1) | ||||
| 			return false; | ||||
| 		_inlineAssembly.annotation().externalReferences[&_identifier] = declarations.front(); | ||||
| 		// At this stage we neither know the code to generate nor the stack size of the identifier,
 | ||||
| 		// so we do not modify assembly.
 | ||||
| 		return true; | ||||
| 	}); | ||||
| 			return size_t(-1); | ||||
| 		_inlineAssembly.annotation().externalReferences[&_identifier].isSlot = isSlot; | ||||
| 		_inlineAssembly.annotation().externalReferences[&_identifier].isOffset = isOffset; | ||||
| 		_inlineAssembly.annotation().externalReferences[&_identifier].declaration = declarations.front(); | ||||
| 		return size_t(1); | ||||
| 	}; | ||||
| 
 | ||||
| 	// Will be re-generated later with correct information
 | ||||
| 	assembly::AsmAnalysisInfo analysisInfo; | ||||
| 	assembly::AsmAnalyzer(analysisInfo, errorsIgnored, resolver).analyze(_inlineAssembly.operations()); | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -24,8 +24,9 @@ | ||||
| #include <memory> | ||||
| #include <boost/range/adaptor/reversed.hpp> | ||||
| #include <libsolidity/ast/AST.h> | ||||
| #include <libevmasm/Assembly.h> // needed for inline assembly
 | ||||
| #include <libsolidity/inlineasm/AsmCodeGen.h> | ||||
| #include <libsolidity/inlineasm/AsmAnalysis.h> | ||||
| #include <libsolidity/inlineasm/AsmAnalysisInfo.h> | ||||
| #include <libsolidity/inlineasm/AsmData.h> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace dev; | ||||
| @ -628,65 +629,91 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType) | ||||
| 
 | ||||
| bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) | ||||
| { | ||||
| 	// Inline assembly does not have its own type-checking phase, so we just run the
 | ||||
| 	// code-generator and see whether it produces any errors.
 | ||||
| 	// External references have already been resolved in a prior stage and stored in the annotation.
 | ||||
| 	auto identifierAccess = [&]( | ||||
| 	// We run the resolve step again regardless.
 | ||||
| 	assembly::ExternalIdentifierAccess::Resolver identifierAccess = [&]( | ||||
| 		assembly::Identifier const& _identifier, | ||||
| 		eth::Assembly& _assembly, | ||||
| 		assembly::CodeGenerator::IdentifierContext _context | ||||
| 		assembly::IdentifierContext _context | ||||
| 	) | ||||
| 	{ | ||||
| 		auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); | ||||
| 		if (ref == _inlineAssembly.annotation().externalReferences.end()) | ||||
| 			return false; | ||||
| 		Declaration const* declaration = ref->second; | ||||
| 			return size_t(-1); | ||||
| 		Declaration const* declaration = ref->second.declaration; | ||||
| 		solAssert(!!declaration, ""); | ||||
| 		if (_context == assembly::CodeGenerator::IdentifierContext::RValue) | ||||
| 		if (auto var = dynamic_cast<VariableDeclaration const*>(declaration)) | ||||
| 		{ | ||||
| 			if (ref->second.isSlot || ref->second.isOffset) | ||||
| 			{ | ||||
| 				if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage)) | ||||
| 				{ | ||||
| 					typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables."); | ||||
| 					return size_t(-1); | ||||
| 				} | ||||
| 				else if (_context != assembly::IdentifierContext::RValue) | ||||
| 				{ | ||||
| 					typeError(_identifier.location, "Storage variables cannot be assigned to."); | ||||
| 					return size_t(-1); | ||||
| 				} | ||||
| 			} | ||||
| 			else if (var->isConstant()) | ||||
| 			{ | ||||
| 				typeError(_identifier.location, "Constant variables not supported by inline assembly."); | ||||
| 				return size_t(-1); | ||||
| 			} | ||||
| 			else if (!var->isLocalVariable()) | ||||
| 			{ | ||||
| 				typeError(_identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes."); | ||||
| 				return size_t(-1); | ||||
| 			} | ||||
| 			else if (var->type()->dataStoredIn(DataLocation::Storage)) | ||||
| 			{ | ||||
| 				typeError(_identifier.location, "You have to use the _slot or _offset prefix to access storage reference variables."); | ||||
| 				return size_t(-1); | ||||
| 			} | ||||
| 			else if (var->type()->sizeOnStack() != 1) | ||||
| 			{ | ||||
| 				typeError(_identifier.location, "Only types that use one stack slot are supported."); | ||||
| 				return size_t(-1); | ||||
| 			} | ||||
| 		} | ||||
| 		else if (_context == assembly::IdentifierContext::LValue) | ||||
| 		{ | ||||
| 			typeError(_identifier.location, "Only local variables can be assigned to in inline assembly."); | ||||
| 			return size_t(-1); | ||||
| 		} | ||||
| 
 | ||||
| 		if (_context == assembly::IdentifierContext::RValue) | ||||
| 		{ | ||||
| 			solAssert(!!declaration->type(), "Type of declaration required but not yet determined."); | ||||
| 			unsigned pushes = 0; | ||||
| 			if (dynamic_cast<FunctionDefinition const*>(declaration)) | ||||
| 				pushes = 1; | ||||
| 			else if (auto var = dynamic_cast<VariableDeclaration const*>(declaration)) | ||||
| 			{ | ||||
| 				if (var->isConstant()) | ||||
| 					fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly."); | ||||
| 				if (var->isLocalVariable()) | ||||
| 					pushes = var->type()->sizeOnStack(); | ||||
| 				else if (!var->type()->isValueType()) | ||||
| 					pushes = 1; | ||||
| 				else | ||||
| 					pushes = 2; // slot number, intra slot offset
 | ||||
| 			} | ||||
| 			else if (dynamic_cast<VariableDeclaration const*>(declaration)) | ||||
| 			{ | ||||
| 			} | ||||
| 			else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration)) | ||||
| 			{ | ||||
| 				if (!contract->isLibrary()) | ||||
| 					return false; | ||||
| 				pushes = 1; | ||||
| 				{ | ||||
| 					typeError(_identifier.location, "Expected a library."); | ||||
| 					return size_t(-1); | ||||
| 				} | ||||
| 			} | ||||
| 			else | ||||
| 				return false; | ||||
| 			for (unsigned i = 0; i < pushes; ++i) | ||||
| 				_assembly.append(u256(0)); // just to verify the stack height
 | ||||
| 				return size_t(-1); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			// lvalue context
 | ||||
| 			if (auto varDecl = dynamic_cast<VariableDeclaration const*>(declaration)) | ||||
| 			{ | ||||
| 				if (!varDecl->isLocalVariable()) | ||||
| 					return false; // only local variables are inline-assemlby lvalues
 | ||||
| 				for (unsigned i = 0; i < declaration->type()->sizeOnStack(); ++i) | ||||
| 					_assembly.append(Instruction::POP); // remove value just to verify the stack height
 | ||||
| 			} | ||||
| 			else | ||||
| 				return false; | ||||
| 		} | ||||
| 		return true; | ||||
| 		ref->second.valueSize = 1; | ||||
| 		return size_t(1); | ||||
| 	}; | ||||
| 	assembly::CodeGenerator codeGen(_inlineAssembly.operations(), m_errors); | ||||
| 	if (!codeGen.typeCheck(identifierAccess)) | ||||
| 	solAssert(!_inlineAssembly.annotation().analysisInfo, ""); | ||||
| 	_inlineAssembly.annotation().analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); | ||||
| 	assembly::AsmAnalyzer analyzer( | ||||
| 		*_inlineAssembly.annotation().analysisInfo, | ||||
| 		m_errors, | ||||
| 		identifierAccess | ||||
| 	); | ||||
| 	if (!analyzer.analyze(_inlineAssembly.operations())) | ||||
| 		return false; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| @ -22,11 +22,12 @@ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <libsolidity/ast/ASTForward.h> | ||||
| 
 | ||||
| #include <map> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| #include <set> | ||||
| #include <libsolidity/ast/ASTForward.h> | ||||
| 
 | ||||
| namespace dev | ||||
| { | ||||
| @ -112,13 +113,24 @@ struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation | ||||
| 
 | ||||
| namespace assembly | ||||
| { | ||||
| struct Identifier; // forward
 | ||||
| 	struct AsmAnalysisInfo; | ||||
| 	struct Identifier; | ||||
| } | ||||
| 
 | ||||
| struct InlineAssemblyAnnotation: StatementAnnotation | ||||
| { | ||||
| 	/// Mapping containing resolved references to external identifiers.
 | ||||
| 	std::map<assembly::Identifier const*, Declaration const*> externalReferences; | ||||
| 	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.
 | ||||
| 		size_t valueSize = size_t(-1); | ||||
| 	}; | ||||
| 
 | ||||
| 	/// Mapping containing resolved references to external identifiers and their value size
 | ||||
| 	std::map<assembly::Identifier const*, ExternalIdentifierInfo> externalReferences; | ||||
| 	/// Information generated during analysis phase.
 | ||||
| 	std::shared_ptr<assembly::AsmAnalysisInfo> analysisInfo; | ||||
| }; | ||||
| 
 | ||||
| struct ReturnAnnotation: StatementAnnotation | ||||
|  | ||||
| @ -265,31 +265,40 @@ void CompilerContext::appendInlineAssembly( | ||||
| 	} | ||||
| 
 | ||||
| 	unsigned startStackHeight = stackHeight(); | ||||
| 	auto identifierAccess = [&]( | ||||
| 
 | ||||
| 	assembly::ExternalIdentifierAccess identifierAccess; | ||||
| 	identifierAccess.resolve = [&]( | ||||
| 		assembly::Identifier const& _identifier, | ||||
| 		eth::Assembly& _assembly, | ||||
| 		assembly::CodeGenerator::IdentifierContext _context | ||||
| 	) { | ||||
| 		assembly::IdentifierContext | ||||
| 	) | ||||
| 	{ | ||||
| 		auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name); | ||||
| 		if (it == _localVariables.end()) | ||||
| 			return false; | ||||
| 		return it == _localVariables.end() ? size_t(-1) : 1; | ||||
| 	}; | ||||
| 	identifierAccess.generateCode = [&]( | ||||
| 		assembly::Identifier const& _identifier, | ||||
| 		assembly::IdentifierContext _context, | ||||
| 		eth::Assembly& _assembly | ||||
| 	) | ||||
| 	{ | ||||
| 		auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name); | ||||
| 		solAssert(it != _localVariables.end(), ""); | ||||
| 		unsigned stackDepth = _localVariables.end() - it; | ||||
| 		int stackDiff = _assembly.deposit() - startStackHeight + stackDepth; | ||||
| 		if (_context == assembly::CodeGenerator::IdentifierContext::LValue) | ||||
| 		if (_context == assembly::IdentifierContext::LValue) | ||||
| 			stackDiff -= 1; | ||||
| 		if (stackDiff < 1 || stackDiff > 16) | ||||
| 			BOOST_THROW_EXCEPTION( | ||||
| 				CompilerError() << | ||||
| 				errinfo_comment("Stack too deep, try removing local variables.") | ||||
| 			); | ||||
| 		if (_context == assembly::CodeGenerator::IdentifierContext::RValue) | ||||
| 		if (_context == assembly::IdentifierContext::RValue) | ||||
| 			_assembly.append(dupInstruction(stackDiff)); | ||||
| 		else | ||||
| 		{ | ||||
| 			_assembly.append(swapInstruction(stackDiff)); | ||||
| 			_assembly.append(Instruction::POP); | ||||
| 		} | ||||
| 		return true; | ||||
| 	}; | ||||
| 
 | ||||
| 	solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), "Failed to assemble inline assembly block."); | ||||
|  | ||||
| @ -520,93 +520,129 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) | ||||
| bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) | ||||
| { | ||||
| 	ErrorList errors; | ||||
| 	assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errors); | ||||
| 	assembly::CodeGenerator codeGen(errors); | ||||
| 	unsigned startStackHeight = m_context.stackHeight(); | ||||
| 	codeGen.assemble( | ||||
| 		m_context.nonConstAssembly(), | ||||
| 		[&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) { | ||||
| 			auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); | ||||
| 			if (ref == _inlineAssembly.annotation().externalReferences.end()) | ||||
| 				return false; | ||||
| 			Declaration const* decl = ref->second; | ||||
| 			solAssert(!!decl, ""); | ||||
| 			if (_context == assembly::CodeGenerator::IdentifierContext::RValue) | ||||
| 	assembly::ExternalIdentifierAccess identifierAccess; | ||||
| 	identifierAccess.resolve = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext) | ||||
| 	{ | ||||
| 		auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); | ||||
| 		if (ref == _inlineAssembly.annotation().externalReferences.end()) | ||||
| 			return size_t(-1); | ||||
| 		return ref->second.valueSize; | ||||
| 	}; | ||||
| 	identifierAccess.generateCode = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext _context, eth::Assembly& _assembly) | ||||
| 	{ | ||||
| 		auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); | ||||
| 		solAssert(ref != _inlineAssembly.annotation().externalReferences.end(), ""); | ||||
| 		Declaration const* decl = ref->second.declaration; | ||||
| 		solAssert(!!decl, ""); | ||||
| 		if (_context == assembly::IdentifierContext::RValue) | ||||
| 		{ | ||||
| 			int const depositBefore = _assembly.deposit(); | ||||
| 			solAssert(!!decl->type(), "Type of declaration required but not yet determined."); | ||||
| 			if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl)) | ||||
| 			{ | ||||
| 				solAssert(!!decl->type(), "Type of declaration required but not yet determined."); | ||||
| 				if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl)) | ||||
| 				solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); | ||||
| 				functionDef = &m_context.resolveVirtualFunction(*functionDef); | ||||
| 				_assembly.append(m_context.functionEntryLabel(*functionDef).pushTag()); | ||||
| 				// If there is a runtime context, we have to merge both labels into the same
 | ||||
| 				// stack slot in case we store it in storage.
 | ||||
| 				if (CompilerContext* rtc = m_context.runtimeContext()) | ||||
| 				{ | ||||
| 					functionDef = &m_context.resolveVirtualFunction(*functionDef); | ||||
| 					_assembly.append(m_context.functionEntryLabel(*functionDef).pushTag()); | ||||
| 					// If there is a runtime context, we have to merge both labels into the same
 | ||||
| 					// stack slot in case we store it in storage.
 | ||||
| 					if (CompilerContext* rtc = m_context.runtimeContext()) | ||||
| 					{ | ||||
| 						_assembly.append(u256(1) << 32); | ||||
| 						_assembly.append(Instruction::MUL); | ||||
| 						_assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub())); | ||||
| 						_assembly.append(Instruction::OR); | ||||
| 					} | ||||
| 					_assembly.append(u256(1) << 32); | ||||
| 					_assembly.append(Instruction::MUL); | ||||
| 					_assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub())); | ||||
| 					_assembly.append(Instruction::OR); | ||||
| 				} | ||||
| 				else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl)) | ||||
| 			} | ||||
| 			else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl)) | ||||
| 			{ | ||||
| 				solAssert(!variable->isConstant(), ""); | ||||
| 				if (m_context.isStateVariable(decl)) | ||||
| 				{ | ||||
| 					solAssert(!variable->isConstant(), ""); | ||||
| 					if (m_context.isLocalVariable(variable)) | ||||
| 					{ | ||||
| 						int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable); | ||||
| 						if (stackDiff < 1 || stackDiff > 16) | ||||
| 							BOOST_THROW_EXCEPTION( | ||||
| 								CompilerError() << | ||||
| 								errinfo_sourceLocation(_inlineAssembly.location()) << | ||||
| 								errinfo_comment("Stack too deep, try removing local variables.") | ||||
| 							); | ||||
| 						for (unsigned i = 0; i < variable->type()->sizeOnStack(); ++i) | ||||
| 							_assembly.append(dupInstruction(stackDiff)); | ||||
| 					} | ||||
| 					auto const& location = m_context.storageLocationOfVariable(*decl); | ||||
| 					if (ref->second.isSlot) | ||||
| 						m_context << location.first; | ||||
| 					else if (ref->second.isOffset) | ||||
| 						m_context << u256(location.second); | ||||
| 					else | ||||
| 						solAssert(false, ""); | ||||
| 				} | ||||
| 				else if (m_context.isLocalVariable(decl)) | ||||
| 				{ | ||||
| 					int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable); | ||||
| 					if (ref->second.isSlot || ref->second.isOffset) | ||||
| 					{ | ||||
| 						solAssert(m_context.isStateVariable(variable), "Invalid variable type."); | ||||
| 						auto const& location = m_context.storageLocationOfVariable(*variable); | ||||
| 						if (!variable->type()->isValueType()) | ||||
| 						solAssert(variable->type()->dataStoredIn(DataLocation::Storage), ""); | ||||
| 						unsigned size = variable->type()->sizeOnStack(); | ||||
| 						if (size == 2) | ||||
| 						{ | ||||
| 							solAssert(location.second == 0, "Intra-slot offest assumed to be zero."); | ||||
| 							_assembly.append(location.first); | ||||
| 							// slot plus offset
 | ||||
| 							if (ref->second.isOffset) | ||||
| 								stackDiff--; | ||||
| 						} | ||||
| 						else | ||||
| 						{ | ||||
| 							_assembly.append(location.first); | ||||
| 							_assembly.append(u256(location.second)); | ||||
| 							solAssert(size == 1, ""); | ||||
| 							// only slot, offset is zero
 | ||||
| 							if (ref->second.isOffset) | ||||
| 							{ | ||||
| 								_assembly.append(u256(0)); | ||||
| 								return; | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 				else if (auto contract = dynamic_cast<ContractDefinition const*>(decl)) | ||||
| 				{ | ||||
| 					solAssert(contract->isLibrary(), ""); | ||||
| 					_assembly.appendLibraryAddress(contract->fullyQualifiedName()); | ||||
| 					else | ||||
| 						solAssert(variable->type()->sizeOnStack() == 1, ""); | ||||
| 					if (stackDiff < 1 || stackDiff > 16) | ||||
| 						BOOST_THROW_EXCEPTION( | ||||
| 							CompilerError() << | ||||
| 							errinfo_sourceLocation(_inlineAssembly.location()) << | ||||
| 							errinfo_comment("Stack too deep, try removing local variables.") | ||||
| 						); | ||||
| 					solAssert(variable->type()->sizeOnStack() == 1, ""); | ||||
| 					_assembly.append(dupInstruction(stackDiff)); | ||||
| 				} | ||||
| 				else | ||||
| 					solAssert(false, "Invalid declaration type."); | ||||
| 			} else { | ||||
| 				// lvalue context
 | ||||
| 				auto variable = dynamic_cast<VariableDeclaration const*>(decl); | ||||
| 				solAssert( | ||||
| 					!!variable && m_context.isLocalVariable(variable), | ||||
| 					"Can only assign to stack variables in inline assembly." | ||||
| 				); | ||||
| 				unsigned size = variable->type()->sizeOnStack(); | ||||
| 				int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - size; | ||||
| 				if (stackDiff > 16 || stackDiff < 1) | ||||
| 					BOOST_THROW_EXCEPTION( | ||||
| 						CompilerError() << | ||||
| 						errinfo_sourceLocation(_inlineAssembly.location()) << | ||||
| 						errinfo_comment("Stack too deep, try removing local variables.") | ||||
| 					); | ||||
| 				for (unsigned i = 0; i < size; ++i) { | ||||
| 					_assembly.append(swapInstruction(stackDiff)); | ||||
| 					_assembly.append(Instruction::POP); | ||||
| 				} | ||||
| 					solAssert(false, ""); | ||||
| 			} | ||||
| 			return true; | ||||
| 			else if (auto contract = dynamic_cast<ContractDefinition const*>(decl)) | ||||
| 			{ | ||||
| 				solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); | ||||
| 				solAssert(contract->isLibrary(), ""); | ||||
| 				_assembly.appendLibraryAddress(contract->fullyQualifiedName()); | ||||
| 			} | ||||
| 			else | ||||
| 				solAssert(false, "Invalid declaration type."); | ||||
| 			solAssert(_assembly.deposit() - depositBefore == int(ref->second.valueSize), ""); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			// lvalue context
 | ||||
| 			solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); | ||||
| 			auto variable = dynamic_cast<VariableDeclaration const*>(decl); | ||||
| 			solAssert( | ||||
| 				!!variable && m_context.isLocalVariable(variable), | ||||
| 				"Can only assign to stack variables in inline assembly." | ||||
| 			); | ||||
| 			solAssert(variable->type()->sizeOnStack() == 1, ""); | ||||
| 			int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - 1; | ||||
| 			if (stackDiff > 16 || stackDiff < 1) | ||||
| 				BOOST_THROW_EXCEPTION( | ||||
| 					CompilerError() << | ||||
| 					errinfo_sourceLocation(_inlineAssembly.location()) << | ||||
| 					errinfo_comment("Stack too deep, try removing local variables.") | ||||
| 				); | ||||
| 			_assembly.append(swapInstruction(stackDiff)); | ||||
| 			_assembly.append(Instruction::POP); | ||||
| 		} | ||||
| 	}; | ||||
| 	solAssert(_inlineAssembly.annotation().analysisInfo, ""); | ||||
| 	codeGen.assemble( | ||||
| 		_inlineAssembly.operations(), | ||||
| 		*_inlineAssembly.annotation().analysisInfo, | ||||
| 		m_context.nonConstAssembly(), | ||||
| 		identifierAccess | ||||
| 	); | ||||
| 	solAssert(Error::containsOnlyWarnings(errors), "Code generation for inline assembly with errors requested."); | ||||
| 	m_context.setStackOffset(startStackHeight); | ||||
|  | ||||
| @ -21,6 +21,9 @@ | ||||
| #include <libsolidity/inlineasm/AsmAnalysis.h> | ||||
| 
 | ||||
| #include <libsolidity/inlineasm/AsmData.h> | ||||
| #include <libsolidity/inlineasm/AsmScopeFiller.h> | ||||
| #include <libsolidity/inlineasm/AsmScope.h> | ||||
| #include <libsolidity/inlineasm/AsmAnalysisInfo.h> | ||||
| 
 | ||||
| #include <libsolidity/interface/Exceptions.h> | ||||
| #include <libsolidity/interface/Utils.h> | ||||
| @ -35,146 +38,363 @@ using namespace dev; | ||||
| using namespace dev::solidity; | ||||
| using namespace dev::solidity::assembly; | ||||
| 
 | ||||
| 
 | ||||
| bool Scope::registerLabel(string const& _name) | ||||
| AsmAnalyzer::AsmAnalyzer( | ||||
| 	AsmAnalysisInfo& _analysisInfo, | ||||
| 	ErrorList& _errors, | ||||
| 	ExternalIdentifierAccess::Resolver const& _resolver | ||||
| ): | ||||
| 	m_resolver(_resolver), m_info(_analysisInfo), m_errors(_errors) | ||||
| { | ||||
| 	if (exists(_name)) | ||||
| } | ||||
| 
 | ||||
| bool AsmAnalyzer::analyze(Block const& _block) | ||||
| { | ||||
| 	if (!(ScopeFiller(m_info.scopes, m_errors))(_block)) | ||||
| 		return false; | ||||
| 	identifiers[_name] = Label(); | ||||
| 
 | ||||
| 	return (*this)(_block); | ||||
| } | ||||
| 
 | ||||
| bool AsmAnalyzer::operator()(Label const& _label) | ||||
| { | ||||
| 	m_info.stackHeightInfo[&_label] = m_stackHeight; | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool Scope::registerVariable(string const& _name) | ||||
| bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction) | ||||
| { | ||||
| 	if (exists(_name)) | ||||
| 		return false; | ||||
| 	identifiers[_name] = Variable(); | ||||
| 	auto const& info = instructionInfo(_instruction.instruction); | ||||
| 	m_stackHeight += info.ret - info.args; | ||||
| 	m_info.stackHeightInfo[&_instruction] = m_stackHeight; | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool Scope::registerFunction(string const& _name, size_t _arguments, size_t _returns) | ||||
| { | ||||
| 	if (exists(_name)) | ||||
| 		return false; | ||||
| 	identifiers[_name] = Function(_arguments, _returns); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| Scope::Identifier* Scope::lookup(string const& _name) | ||||
| { | ||||
| 	if (identifiers.count(_name)) | ||||
| 		return &identifiers[_name]; | ||||
| 	else if (superScope && !closedScope) | ||||
| 		return superScope->lookup(_name); | ||||
| 	else | ||||
| 		return nullptr; | ||||
| } | ||||
| 
 | ||||
| bool Scope::exists(string const& _name) | ||||
| { | ||||
| 	if (identifiers.count(_name)) | ||||
| 		return true; | ||||
| 	else if (superScope) | ||||
| 		return superScope->exists(_name); | ||||
| 	else | ||||
| 		return false; | ||||
| } | ||||
| 
 | ||||
| AsmAnalyzer::AsmAnalyzer(AsmAnalyzer::Scopes& _scopes, ErrorList& _errors): | ||||
| 	m_scopes(_scopes), m_errors(_errors) | ||||
| { | ||||
| 	// Make the Solidity ErrorTag available to inline assembly
 | ||||
| 	m_scopes[nullptr] = make_shared<Scope>(); | ||||
| 	Scope::Label errorLabel; | ||||
| 	errorLabel.id = Scope::Label::errorLabelId; | ||||
| 	m_scopes[nullptr]->identifiers["invalidJumpLabel"] = errorLabel; | ||||
| 	m_currentScope = m_scopes[nullptr].get(); | ||||
| } | ||||
| 
 | ||||
| bool AsmAnalyzer::operator()(assembly::Literal const& _literal) | ||||
| { | ||||
| 	++m_stackHeight; | ||||
| 	if (!_literal.isNumber && _literal.value.size() > 32) | ||||
| 	{ | ||||
| 		m_errors.push_back(make_shared<Error>( | ||||
| 			Error::Type::TypeError, | ||||
| 			"String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)" | ||||
| 			"String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)", | ||||
| 			_literal.location | ||||
| 		)); | ||||
| 		return false; | ||||
| 	} | ||||
| 	m_info.stackHeightInfo[&_literal] = m_stackHeight; | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier) | ||||
| { | ||||
| 	size_t numErrorsBefore = m_errors.size(); | ||||
| 	bool success = true; | ||||
| 	if (m_currentScope->lookup(_identifier.name, Scope::Visitor( | ||||
| 		[&](Scope::Variable const& _var) | ||||
| 		{ | ||||
| 			if (!_var.active) | ||||
| 			{ | ||||
| 				m_errors.push_back(make_shared<Error>( | ||||
| 					Error::Type::DeclarationError, | ||||
| 					"Variable " + _identifier.name + " used before it was declared.", | ||||
| 					_identifier.location | ||||
| 				)); | ||||
| 				success = false; | ||||
| 			} | ||||
| 			++m_stackHeight; | ||||
| 		}, | ||||
| 		[&](Scope::Label const&) | ||||
| 		{ | ||||
| 			++m_stackHeight; | ||||
| 		}, | ||||
| 		[&](Scope::Function const&) | ||||
| 		{ | ||||
| 			m_errors.push_back(make_shared<Error>( | ||||
| 				Error::Type::TypeError, | ||||
| 				"Function " + _identifier.name + " used without being called.", | ||||
| 				_identifier.location | ||||
| 			)); | ||||
| 			success = false; | ||||
| 		} | ||||
| 	))) | ||||
| 	{ | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		size_t stackSize(-1); | ||||
| 		if (m_resolver) | ||||
| 			stackSize = m_resolver(_identifier, IdentifierContext::RValue); | ||||
| 		if (stackSize == size_t(-1)) | ||||
| 		{ | ||||
| 			// Only add an error message if the callback did not do it.
 | ||||
| 			if (numErrorsBefore == m_errors.size()) | ||||
| 				m_errors.push_back(make_shared<Error>( | ||||
| 					Error::Type::DeclarationError, | ||||
| 					"Identifier not found.", | ||||
| 					_identifier.location | ||||
| 				)); | ||||
| 			success = false; | ||||
| 		} | ||||
| 		m_stackHeight += stackSize == size_t(-1) ? 1 : stackSize; | ||||
| 	} | ||||
| 	m_info.stackHeightInfo[&_identifier] = m_stackHeight; | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) | ||||
| { | ||||
| 	bool success = true; | ||||
| 	for (auto const& arg: _instr.arguments | boost::adaptors::reversed) | ||||
| 	{ | ||||
| 		int const stackHeight = m_stackHeight; | ||||
| 		if (!boost::apply_visitor(*this, arg)) | ||||
| 			success = false; | ||||
| 		if (!expectDeposit(1, stackHeight, locationOf(arg))) | ||||
| 			success = false; | ||||
| 	} | ||||
| 	// Parser already checks that the number of arguments is correct.
 | ||||
| 	solAssert(instructionInfo(_instr.instruction.instruction).args == int(_instr.arguments.size()), ""); | ||||
| 	if (!(*this)(_instr.instruction)) | ||||
| 		success = false; | ||||
| 	m_info.stackHeightInfo[&_instr] = m_stackHeight; | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| bool AsmAnalyzer::operator()(Label const& _item) | ||||
| bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment) | ||||
| { | ||||
| 	if (!m_currentScope->registerLabel(_item.name)) | ||||
| 	{ | ||||
| 		//@TODO secondary location
 | ||||
| 		m_errors.push_back(make_shared<Error>( | ||||
| 			Error::Type::DeclarationError, | ||||
| 			"Label name " + _item.name + " already taken in this scope.", | ||||
| 			_item.location | ||||
| 		)); | ||||
| 		return false; | ||||
| 	} | ||||
| 	return true; | ||||
| 	bool success = checkAssignment(_assignment.variableName, size_t(-1)); | ||||
| 	m_info.stackHeightInfo[&_assignment] = m_stackHeight; | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment) | ||||
| { | ||||
| 	return boost::apply_visitor(*this, *_assignment.value); | ||||
| 	int const stackHeight = m_stackHeight; | ||||
| 	bool success = boost::apply_visitor(*this, *_assignment.value); | ||||
| 	solAssert(m_stackHeight >= stackHeight, "Negative value size."); | ||||
| 	if (!checkAssignment(_assignment.variableName, m_stackHeight - stackHeight)) | ||||
| 		success = false; | ||||
| 	m_info.stackHeightInfo[&_assignment] = m_stackHeight; | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl) | ||||
| { | ||||
| 	int const stackHeight = m_stackHeight; | ||||
| 	bool success = boost::apply_visitor(*this, *_varDecl.value); | ||||
| 	if (!m_currentScope->registerVariable(_varDecl.name)) | ||||
| 	{ | ||||
| 		//@TODO secondary location
 | ||||
| 		m_errors.push_back(make_shared<Error>( | ||||
| 			Error::Type::DeclarationError, | ||||
| 			"Variable name " + _varDecl.name + " already taken in this scope.", | ||||
| 			_varDecl.location | ||||
| 		)); | ||||
| 		success = false; | ||||
| 	} | ||||
| 	solAssert(m_stackHeight - stackHeight == 1, "Invalid value size."); | ||||
| 	boost::get<Scope::Variable>(m_currentScope->identifiers.at(_varDecl.name)).active = true; | ||||
| 	m_info.stackHeightInfo[&_varDecl] = m_stackHeight; | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| bool AsmAnalyzer::operator()(assembly::FunctionDefinition const&) | ||||
| bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef) | ||||
| { | ||||
| 	// TODO - we cannot throw an exception here because of some tests.
 | ||||
| 	return true; | ||||
| 	Scope& bodyScope = scope(&_funDef.body); | ||||
| 	for (auto const& var: _funDef.arguments + _funDef.returns) | ||||
| 		boost::get<Scope::Variable>(bodyScope.identifiers.at(var)).active = true; | ||||
| 
 | ||||
| 	int const stackHeight = m_stackHeight; | ||||
| 	m_stackHeight = _funDef.arguments.size() + _funDef.returns.size(); | ||||
| 	m_virtualVariablesInNextBlock = m_stackHeight; | ||||
| 
 | ||||
| 	bool success = (*this)(_funDef.body); | ||||
| 
 | ||||
| 	m_stackHeight = stackHeight; | ||||
| 	m_info.stackHeightInfo[&_funDef] = m_stackHeight; | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| bool AsmAnalyzer::operator()(assembly::FunctionCall const&) | ||||
| bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall) | ||||
| { | ||||
| 	// TODO - we cannot throw an exception here because of some tests.
 | ||||
| 	return true; | ||||
| 	bool success = true; | ||||
| 	size_t arguments = 0; | ||||
| 	size_t returns = 0; | ||||
| 	if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor( | ||||
| 		[&](Scope::Variable const&) | ||||
| 		{ | ||||
| 			m_errors.push_back(make_shared<Error>( | ||||
| 				Error::Type::TypeError, | ||||
| 				"Attempt to call variable instead of function.", | ||||
| 				_funCall.functionName.location | ||||
| 			)); | ||||
| 			success = false; | ||||
| 		}, | ||||
| 		[&](Scope::Label const&) | ||||
| 		{ | ||||
| 			m_errors.push_back(make_shared<Error>( | ||||
| 				Error::Type::TypeError, | ||||
| 				"Attempt to call label instead of function.", | ||||
| 				_funCall.functionName.location | ||||
| 				)); | ||||
| 			success = false; | ||||
| 		}, | ||||
| 		[&](Scope::Function const& _fun) | ||||
| 		{ | ||||
| 			arguments = _fun.arguments; | ||||
| 			returns = _fun.returns; | ||||
| 		} | ||||
| 	))) | ||||
| 	{ | ||||
| 		m_errors.push_back(make_shared<Error>( | ||||
| 			Error::Type::DeclarationError, | ||||
| 			"Function not found.", | ||||
| 			_funCall.functionName.location | ||||
| 		)); | ||||
| 		success = false; | ||||
| 	} | ||||
| 	if (success) | ||||
| 	{ | ||||
| 		if (_funCall.arguments.size() != arguments) | ||||
| 		{ | ||||
| 			m_errors.push_back(make_shared<Error>( | ||||
| 				Error::Type::TypeError, | ||||
| 				"Expected " + | ||||
| 				boost::lexical_cast<string>(arguments) + | ||||
| 				" arguments but got " + | ||||
| 				boost::lexical_cast<string>(_funCall.arguments.size()) + | ||||
| 				".", | ||||
| 				_funCall.functionName.location | ||||
| 				)); | ||||
| 			success = false; | ||||
| 		} | ||||
| 	} | ||||
| 	for (auto const& arg: _funCall.arguments | boost::adaptors::reversed) | ||||
| 	{ | ||||
| 		int const stackHeight = m_stackHeight; | ||||
| 		if (!boost::apply_visitor(*this, arg)) | ||||
| 			success = false; | ||||
| 		if (!expectDeposit(1, stackHeight, locationOf(arg))) | ||||
| 			success = false; | ||||
| 	} | ||||
| 	m_stackHeight += int(returns) - int(arguments); | ||||
| 	m_info.stackHeightInfo[&_funCall] = m_stackHeight; | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| bool AsmAnalyzer::operator()(Block const& _block) | ||||
| { | ||||
| 	bool success = true; | ||||
| 	auto scope = make_shared<Scope>(); | ||||
| 	scope->superScope = m_currentScope; | ||||
| 	m_scopes[&_block] = scope; | ||||
| 	m_currentScope = scope.get(); | ||||
| 	m_currentScope = &scope(&_block); | ||||
| 
 | ||||
| 	int const initialStackHeight = m_stackHeight - m_virtualVariablesInNextBlock; | ||||
| 	m_virtualVariablesInNextBlock = 0; | ||||
| 
 | ||||
| 	for (auto const& s: _block.statements) | ||||
| 		if (!boost::apply_visitor(*this, s)) | ||||
| 			success = false; | ||||
| 
 | ||||
| 	for (auto const& identifier: scope(&_block).identifiers) | ||||
| 		if (identifier.second.type() == typeid(Scope::Variable)) | ||||
| 			--m_stackHeight; | ||||
| 
 | ||||
| 	int const stackDiff = m_stackHeight - initialStackHeight; | ||||
| 	if (stackDiff != 0) | ||||
| 	{ | ||||
| 		m_errors.push_back(make_shared<Error>( | ||||
| 			Error::Type::DeclarationError, | ||||
| 			"Unbalanced stack at the end of a block: " + | ||||
| 			( | ||||
| 				stackDiff > 0 ? | ||||
| 				to_string(stackDiff) + string(" surplus item(s).") : | ||||
| 				to_string(-stackDiff) + string(" missing item(s).") | ||||
| 			), | ||||
| 			_block.location | ||||
| 		)); | ||||
| 		success = false; | ||||
| 	} | ||||
| 
 | ||||
| 	m_currentScope = m_currentScope->superScope; | ||||
| 	m_info.stackHeightInfo[&_block] = m_stackHeight; | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize) | ||||
| { | ||||
| 	bool success = true; | ||||
| 	size_t numErrorsBefore = m_errors.size(); | ||||
| 	size_t variableSize(-1); | ||||
| 	if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name)) | ||||
| 	{ | ||||
| 		// Check that it is a variable
 | ||||
| 		if (var->type() != typeid(Scope::Variable)) | ||||
| 		{ | ||||
| 			m_errors.push_back(make_shared<Error>( | ||||
| 				Error::Type::TypeError, | ||||
| 				"Assignment requires variable.", | ||||
| 				_variable.location | ||||
| 			)); | ||||
| 			success = false; | ||||
| 		} | ||||
| 		else if (!boost::get<Scope::Variable>(*var).active) | ||||
| 		{ | ||||
| 			m_errors.push_back(make_shared<Error>( | ||||
| 				Error::Type::DeclarationError, | ||||
| 				"Variable " + _variable.name + " used before it was declared.", | ||||
| 				_variable.location | ||||
| 			)); | ||||
| 			success = false; | ||||
| 		} | ||||
| 		variableSize = 1; | ||||
| 	} | ||||
| 	else if (m_resolver) | ||||
| 		variableSize = m_resolver(_variable, IdentifierContext::LValue); | ||||
| 	if (variableSize == size_t(-1)) | ||||
| 	{ | ||||
| 		// Only add message if the callback did not.
 | ||||
| 		if (numErrorsBefore == m_errors.size()) | ||||
| 			m_errors.push_back(make_shared<Error>( | ||||
| 				Error::Type::DeclarationError, | ||||
| 				"Variable not found or variable not lvalue.", | ||||
| 				_variable.location | ||||
| 			)); | ||||
| 		success = false; | ||||
| 	} | ||||
| 	if (_valueSize == size_t(-1)) | ||||
| 		_valueSize = variableSize == size_t(-1) ? 1 : variableSize; | ||||
| 
 | ||||
| 	m_stackHeight -= _valueSize; | ||||
| 
 | ||||
| 	if (_valueSize != variableSize && variableSize != size_t(-1)) | ||||
| 	{ | ||||
| 		m_errors.push_back(make_shared<Error>( | ||||
| 			Error::Type::TypeError, | ||||
| 			"Variable size (" + | ||||
| 			to_string(variableSize) + | ||||
| 			") and value size (" + | ||||
| 			to_string(_valueSize) + | ||||
| 			") do not match.", | ||||
| 			_variable.location | ||||
| 		)); | ||||
| 		success = false; | ||||
| 	} | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| bool AsmAnalyzer::expectDeposit(int const _deposit, int const _oldHeight, SourceLocation const& _location) | ||||
| { | ||||
| 	int stackDiff = m_stackHeight - _oldHeight; | ||||
| 	if (stackDiff != _deposit) | ||||
| 	{ | ||||
| 		m_errors.push_back(make_shared<Error>( | ||||
| 			Error::Type::TypeError, | ||||
| 			"Expected instruction(s) to deposit " + | ||||
| 			boost::lexical_cast<string>(_deposit) + | ||||
| 			" item(s) to the stack, but did deposit " + | ||||
| 			boost::lexical_cast<string>(stackDiff) + | ||||
| 			" item(s).", | ||||
| 			_location | ||||
| 		)); | ||||
| 		return false; | ||||
| 	} | ||||
| 	else | ||||
| 		return true; | ||||
| } | ||||
| 
 | ||||
| Scope& AsmAnalyzer::scope(Block const* _block) | ||||
| { | ||||
| 	auto scopePtr = m_info.scopes.at(_block); | ||||
| 	solAssert(scopePtr, "Scope requested but not present."); | ||||
| 	return *scopePtr; | ||||
| } | ||||
|  | ||||
| @ -20,6 +20,8 @@ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <libsolidity/inlineasm/AsmStack.h> | ||||
| 
 | ||||
| #include <libsolidity/interface/Exceptions.h> | ||||
| 
 | ||||
| #include <boost/variant.hpp> | ||||
| @ -46,101 +48,32 @@ struct Assignment; | ||||
| struct FunctionDefinition; | ||||
| struct FunctionCall; | ||||
| 
 | ||||
| template <class...> | ||||
| struct GenericVisitor{}; | ||||
| 
 | ||||
| template <class Visitable, class... Others> | ||||
| struct GenericVisitor<Visitable, Others...>: public GenericVisitor<Others...> | ||||
| { | ||||
| 	using GenericVisitor<Others...>::operator (); | ||||
| 	explicit GenericVisitor( | ||||
| 		std::function<void(Visitable&)> _visitor, | ||||
| 		std::function<void(Others&)>... _otherVisitors | ||||
| 	): | ||||
| 		GenericVisitor<Others...>(_otherVisitors...), | ||||
| 		m_visitor(_visitor) | ||||
| 	{} | ||||
| 
 | ||||
| 	void operator()(Visitable& _v) const { m_visitor(_v); } | ||||
| 
 | ||||
| 	std::function<void(Visitable&)> m_visitor; | ||||
| }; | ||||
| template <> | ||||
| struct GenericVisitor<>: public boost::static_visitor<> { | ||||
| 	void operator()() const {} | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| struct Scope | ||||
| { | ||||
| 	struct Variable | ||||
| 	{ | ||||
| 		int stackHeight = 0; | ||||
| 		bool active = false; | ||||
| 	}; | ||||
| 
 | ||||
| 	struct Label | ||||
| 	{ | ||||
| 		size_t id = unassignedLabelId; | ||||
| 		static const size_t errorLabelId = -1; | ||||
| 		static const size_t unassignedLabelId = 0; | ||||
| 	}; | ||||
| 
 | ||||
| 	struct Function | ||||
| 	{ | ||||
| 		Function(size_t _arguments, size_t _returns): arguments(_arguments), returns(_returns) {} | ||||
| 		size_t arguments = 0; | ||||
| 		size_t returns = 0; | ||||
| 	}; | ||||
| 
 | ||||
| 	using Identifier = boost::variant<Variable, Label, Function>; | ||||
| 	using Visitor = GenericVisitor<Variable const, Label const, Function const>; | ||||
| 	using NonconstVisitor = GenericVisitor<Variable, Label, Function>; | ||||
| 
 | ||||
| 	bool registerVariable(std::string const& _name); | ||||
| 	bool registerLabel(std::string const& _name); | ||||
| 	bool registerFunction(std::string const& _name, size_t _arguments, size_t _returns); | ||||
| 
 | ||||
| 	/// Looks up the identifier in this or super scopes (stops and function and assembly boundaries)
 | ||||
| 	/// and returns a valid pointer if found or a nullptr if not found.
 | ||||
| 	/// The pointer will be invalidated if the scope is modified.
 | ||||
| 	Identifier* lookup(std::string const& _name); | ||||
| 	/// Looks up the identifier in this and super scopes (stops and function and assembly boundaries)
 | ||||
| 	/// and calls the visitor, returns false if not found.
 | ||||
| 	template <class V> | ||||
| 	bool lookup(std::string const& _name, V const& _visitor) | ||||
| 	{ | ||||
| 		if (Identifier* id = lookup(_name)) | ||||
| 		{ | ||||
| 			boost::apply_visitor(_visitor, *id); | ||||
| 			return true; | ||||
| 		} | ||||
| 		else | ||||
| 			return false; | ||||
| 	} | ||||
| 	/// @returns true if the name exists in this scope or in super scopes (also searches
 | ||||
| 	/// across function and assembly boundaries).
 | ||||
| 	bool exists(std::string const& _name); | ||||
| 	Scope* superScope = nullptr; | ||||
| 	/// If true, identifiers from the super scope are not visible here, but they are still
 | ||||
| 	/// taken into account to prevent shadowing.
 | ||||
| 	bool closedScope = false; | ||||
| 	std::map<std::string, Identifier> identifiers; | ||||
| }; | ||||
| struct Scope; | ||||
| 
 | ||||
| struct AsmAnalysisInfo; | ||||
| 
 | ||||
| /**
 | ||||
|  * Performs the full analysis stage, calls the ScopeFiller internally, then resolves | ||||
|  * references and performs other checks. | ||||
|  * If all these checks pass, code generation should not throw errors. | ||||
|  */ | ||||
| class AsmAnalyzer: public boost::static_visitor<bool> | ||||
| { | ||||
| public: | ||||
| 	using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>; | ||||
| 	AsmAnalyzer(Scopes& _scopes, ErrorList& _errors); | ||||
| 	AsmAnalyzer( | ||||
| 		AsmAnalysisInfo& _analysisInfo, | ||||
| 		ErrorList& _errors, | ||||
| 		ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver() | ||||
| 	); | ||||
| 
 | ||||
| 	bool operator()(assembly::Instruction const&) { return true; } | ||||
| 	bool analyze(assembly::Block const& _block); | ||||
| 
 | ||||
| 	bool operator()(assembly::Instruction const&); | ||||
| 	bool operator()(assembly::Literal const& _literal); | ||||
| 	bool operator()(assembly::Identifier const&) { return true; } | ||||
| 	bool operator()(assembly::Identifier const&); | ||||
| 	bool operator()(assembly::FunctionalInstruction const& _functionalInstruction); | ||||
| 	bool operator()(assembly::Label const& _label); | ||||
| 	bool operator()(assembly::Assignment const&) { return true; } | ||||
| 	bool operator()(assembly::Assignment const&); | ||||
| 	bool operator()(assembly::FunctionalAssignment const& _functionalAssignment); | ||||
| 	bool operator()(assembly::VariableDeclaration const& _variableDeclaration); | ||||
| 	bool operator()(assembly::FunctionDefinition const& _functionDefinition); | ||||
| @ -148,8 +81,20 @@ public: | ||||
| 	bool operator()(assembly::Block const& _block); | ||||
| 
 | ||||
| private: | ||||
| 	/// Verifies that a variable to be assigned to exists and has the same size
 | ||||
| 	/// as the value, @a _valueSize, unless that is equal to -1.
 | ||||
| 	bool checkAssignment(assembly::Identifier const& _assignment, size_t _valueSize = size_t(-1)); | ||||
| 	bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location); | ||||
| 	Scope& scope(assembly::Block const* _block); | ||||
| 
 | ||||
| 	/// This is used when we enter the body of a function definition. There, the parameters
 | ||||
| 	/// and return parameters appear as variables which are already on the stack before
 | ||||
| 	/// we enter the block.
 | ||||
| 	int m_virtualVariablesInNextBlock = 0; | ||||
| 	int m_stackHeight = 0; | ||||
| 	ExternalIdentifierAccess::Resolver const& m_resolver; | ||||
| 	Scope* m_currentScope = nullptr; | ||||
| 	Scopes& m_scopes; | ||||
| 	AsmAnalysisInfo& m_info; | ||||
| 	ErrorList& m_errors; | ||||
| }; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										26
									
								
								libsolidity/inlineasm/AsmAnalysisInfo.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								libsolidity/inlineasm/AsmAnalysisInfo.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| /*
 | ||||
| 	This file is part of solidity. | ||||
| 
 | ||||
| 	solidity is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 
 | ||||
| 	solidity is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| /**
 | ||||
|  * Information generated during analyzer part of inline assembly. | ||||
|  */ | ||||
| 
 | ||||
| #include <libsolidity/inlineasm/AsmAnalysisInfo.h> | ||||
| 
 | ||||
| #include <libsolidity/inlineasm/AsmScope.h> | ||||
| 
 | ||||
| #include <ostream> | ||||
| 
 | ||||
							
								
								
									
										61
									
								
								libsolidity/inlineasm/AsmAnalysisInfo.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								libsolidity/inlineasm/AsmAnalysisInfo.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | ||||
| /*
 | ||||
| 	This file is part of solidity. | ||||
| 
 | ||||
| 	solidity is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 
 | ||||
| 	solidity is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| /**
 | ||||
|  * Information generated during analyzer part of inline assembly. | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <boost/variant.hpp> | ||||
| 
 | ||||
| #include <map> | ||||
| #include <memory> | ||||
| 
 | ||||
| namespace dev | ||||
| { | ||||
| namespace solidity | ||||
| { | ||||
| namespace assembly | ||||
| { | ||||
| 
 | ||||
| struct Literal; | ||||
| struct Block; | ||||
| struct Label; | ||||
| struct FunctionalInstruction; | ||||
| struct FunctionalAssignment; | ||||
| struct VariableDeclaration; | ||||
| struct Instruction; | ||||
| struct Identifier; | ||||
| struct Assignment; | ||||
| struct FunctionDefinition; | ||||
| struct FunctionCall; | ||||
| 
 | ||||
| struct Scope; | ||||
| 
 | ||||
| using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Block>; | ||||
| 
 | ||||
| struct AsmAnalysisInfo | ||||
| { | ||||
| 	using StackHeightInfo = std::map<void const*, int>; | ||||
| 	using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>; | ||||
| 	Scopes scopes; | ||||
| 	StackHeightInfo stackHeightInfo; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
| } | ||||
| @ -24,7 +24,9 @@ | ||||
| 
 | ||||
| #include <libsolidity/inlineasm/AsmParser.h> | ||||
| #include <libsolidity/inlineasm/AsmData.h> | ||||
| #include <libsolidity/inlineasm/AsmScope.h> | ||||
| #include <libsolidity/inlineasm/AsmAnalysis.h> | ||||
| #include <libsolidity/inlineasm/AsmAnalysisInfo.h> | ||||
| 
 | ||||
| #include <libevmasm/Assembly.h> | ||||
| #include <libevmasm/SourceLocation.h> | ||||
| @ -46,13 +48,8 @@ using namespace dev::solidity::assembly; | ||||
| 
 | ||||
| struct GeneratorState | ||||
| { | ||||
| 	GeneratorState(ErrorList& _errors, eth::Assembly& _assembly): | ||||
| 		errors(_errors), assembly(_assembly) {} | ||||
| 
 | ||||
| 	void addError(Error::Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation()) | ||||
| 	{ | ||||
| 		errors.push_back(make_shared<Error>(_type, _description, _location)); | ||||
| 	} | ||||
| 	GeneratorState(ErrorList& _errors, AsmAnalysisInfo& _analysisInfo, eth::Assembly& _assembly): | ||||
| 		errors(_errors), info(_analysisInfo), assembly(_assembly) {} | ||||
| 
 | ||||
| 	size_t newLabelId() | ||||
| 	{ | ||||
| @ -66,8 +63,8 @@ struct GeneratorState | ||||
| 		return size_t(id); | ||||
| 	} | ||||
| 
 | ||||
| 	std::map<assembly::Block const*, shared_ptr<Scope>> scopes; | ||||
| 	ErrorList& errors; | ||||
| 	AsmAnalysisInfo info; | ||||
| 	eth::Assembly& assembly; | ||||
| }; | ||||
| 
 | ||||
| @ -80,13 +77,24 @@ public: | ||||
| 	explicit CodeTransform( | ||||
| 		GeneratorState& _state, | ||||
| 		assembly::Block const& _block, | ||||
| 		assembly::CodeGenerator::IdentifierAccess const& _identifierAccess = assembly::CodeGenerator::IdentifierAccess() | ||||
| 		assembly::ExternalIdentifierAccess const& _identifierAccess = assembly::ExternalIdentifierAccess() | ||||
| 	): CodeTransform(_state, _block, _identifierAccess, _state.assembly.deposit()) | ||||
| 	{ | ||||
| 	} | ||||
| 
 | ||||
| private: | ||||
| 	CodeTransform( | ||||
| 		GeneratorState& _state, | ||||
| 		assembly::Block const& _block, | ||||
| 		assembly::ExternalIdentifierAccess const& _identifierAccess, | ||||
| 		int _initialDeposit | ||||
| 	): | ||||
| 		m_state(_state), | ||||
| 		m_scope(*m_state.scopes.at(&_block)), | ||||
| 		m_initialDeposit(m_state.assembly.deposit()), | ||||
| 		m_identifierAccess(_identifierAccess) | ||||
| 		m_scope(*m_state.info.scopes.at(&_block)), | ||||
| 		m_identifierAccess(_identifierAccess), | ||||
| 		m_initialDeposit(_initialDeposit) | ||||
| 	{ | ||||
| 		int blockStartDeposit = m_state.assembly.deposit(); | ||||
| 		std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this)); | ||||
| 
 | ||||
| 		m_state.assembly.setSourceLocation(_block.location); | ||||
| @ -96,31 +104,16 @@ public: | ||||
| 			if (identifier.second.type() == typeid(Scope::Variable)) | ||||
| 				m_state.assembly.append(solidity::Instruction::POP); | ||||
| 
 | ||||
| 		int deposit = m_state.assembly.deposit() - m_initialDeposit; | ||||
| 
 | ||||
| 		// issue warnings for stack height discrepancies
 | ||||
| 		if (deposit < 0) | ||||
| 		{ | ||||
| 			m_state.addError( | ||||
| 				Error::Type::Warning, | ||||
| 				"Inline assembly block is not balanced. It takes " + toString(-deposit) + " item(s) from the stack.", | ||||
| 				_block.location | ||||
| 			); | ||||
| 		} | ||||
| 		else if (deposit > 0) | ||||
| 		{ | ||||
| 			m_state.addError( | ||||
| 				Error::Type::Warning, | ||||
| 				"Inline assembly block is not balanced. It leaves " + toString(deposit) + " item(s) on the stack.", | ||||
| 				_block.location | ||||
| 			); | ||||
| 		} | ||||
| 		int deposit = m_state.assembly.deposit() - blockStartDeposit; | ||||
| 		solAssert(deposit == 0, "Invalid stack height at end of block."); | ||||
| 	} | ||||
| 
 | ||||
| public: | ||||
| 	void operator()(assembly::Instruction const& _instruction) | ||||
| 	{ | ||||
| 		m_state.assembly.setSourceLocation(_instruction.location); | ||||
| 		m_state.assembly.append(_instruction.instruction); | ||||
| 		checkStackHeight(&_instruction); | ||||
| 	} | ||||
| 	void operator()(assembly::Literal const& _literal) | ||||
| 	{ | ||||
| @ -132,6 +125,7 @@ public: | ||||
| 			solAssert(_literal.value.size() <= 32, ""); | ||||
| 			m_state.assembly.append(u256(h256(_literal.value, h256::FromBinary, h256::AlignLeft))); | ||||
| 		} | ||||
| 		checkStackHeight(&_literal); | ||||
| 	} | ||||
| 	void operator()(assembly::Identifier const& _identifier) | ||||
| 	{ | ||||
| @ -153,20 +147,18 @@ public: | ||||
| 			}, | ||||
| 			[=](Scope::Function&) | ||||
| 			{ | ||||
| 				solAssert(false, "Not yet implemented"); | ||||
| 				solAssert(false, "Function not removed during desugaring."); | ||||
| 			} | ||||
| 		))) | ||||
| 		{ | ||||
| 			return; | ||||
| 		} | ||||
| 		else if (!m_identifierAccess || !m_identifierAccess(_identifier, m_state.assembly, CodeGenerator::IdentifierContext::RValue)) | ||||
| 		{ | ||||
| 			m_state.addError( | ||||
| 				Error::Type::DeclarationError, | ||||
| 				"Identifier not found or not unique", | ||||
| 				_identifier.location | ||||
| 			); | ||||
| 			m_state.assembly.append(u256(0)); | ||||
| 		} | ||||
| 		solAssert( | ||||
| 			m_identifierAccess.generateCode, | ||||
| 			"Identifier not found and no external access available." | ||||
| 		); | ||||
| 		m_identifierAccess.generateCode(_identifier, IdentifierContext::RValue, m_state.assembly); | ||||
| 		checkStackHeight(&_identifier); | ||||
| 	} | ||||
| 	void operator()(FunctionalInstruction const& _instr) | ||||
| 	{ | ||||
| @ -174,9 +166,10 @@ public: | ||||
| 		{ | ||||
| 			int height = m_state.assembly.deposit(); | ||||
| 			boost::apply_visitor(*this, *it); | ||||
| 			expectDeposit(1, height, locationOf(*it)); | ||||
| 			expectDeposit(1, height); | ||||
| 		} | ||||
| 		(*this)(_instr.instruction); | ||||
| 		checkStackHeight(&_instr); | ||||
| 	} | ||||
| 	void operator()(assembly::FunctionCall const&) | ||||
| 	{ | ||||
| @ -186,36 +179,39 @@ public: | ||||
| 	{ | ||||
| 		m_state.assembly.setSourceLocation(_label.location); | ||||
| 		solAssert(m_scope.identifiers.count(_label.name), ""); | ||||
| 		Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers[_label.name]); | ||||
| 		Scope::Label& label = boost::get<Scope::Label>(m_scope.identifiers.at(_label.name)); | ||||
| 		assignLabelIdIfUnset(label); | ||||
| 		m_state.assembly.append(eth::AssemblyItem(eth::Tag, label.id)); | ||||
| 		checkStackHeight(&_label); | ||||
| 	} | ||||
| 	void operator()(assembly::Assignment const& _assignment) | ||||
| 	{ | ||||
| 		m_state.assembly.setSourceLocation(_assignment.location); | ||||
| 		generateAssignment(_assignment.variableName, _assignment.location); | ||||
| 		checkStackHeight(&_assignment); | ||||
| 	} | ||||
| 	void operator()(FunctionalAssignment const& _assignment) | ||||
| 	{ | ||||
| 		int height = m_state.assembly.deposit(); | ||||
| 		boost::apply_visitor(*this, *_assignment.value); | ||||
| 		expectDeposit(1, height, locationOf(*_assignment.value)); | ||||
| 		expectDeposit(1, height); | ||||
| 		m_state.assembly.setSourceLocation(_assignment.location); | ||||
| 		generateAssignment(_assignment.variableName, _assignment.location); | ||||
| 		checkStackHeight(&_assignment); | ||||
| 	} | ||||
| 	void operator()(assembly::VariableDeclaration const& _varDecl) | ||||
| 	{ | ||||
| 		int height = m_state.assembly.deposit(); | ||||
| 		boost::apply_visitor(*this, *_varDecl.value); | ||||
| 		expectDeposit(1, height, locationOf(*_varDecl.value)); | ||||
| 		solAssert(m_scope.identifiers.count(_varDecl.name), ""); | ||||
| 		auto& var = boost::get<Scope::Variable>(m_scope.identifiers[_varDecl.name]); | ||||
| 		expectDeposit(1, height); | ||||
| 		auto& var = boost::get<Scope::Variable>(m_scope.identifiers.at(_varDecl.name)); | ||||
| 		var.stackHeight = height; | ||||
| 		var.active = true; | ||||
| 	} | ||||
| 	void operator()(assembly::Block const& _block) | ||||
| 	{ | ||||
| 		CodeTransform(m_state, _block, m_identifierAccess); | ||||
| 		CodeTransform(m_state, _block, m_identifierAccess, m_initialDeposit); | ||||
| 		checkStackHeight(&_block); | ||||
| 	} | ||||
| 	void operator()(assembly::FunctionDefinition const&) | ||||
| 	{ | ||||
| @ -225,35 +221,22 @@ public: | ||||
| private: | ||||
| 	void generateAssignment(assembly::Identifier const& _variableName, SourceLocation const& _location) | ||||
| 	{ | ||||
| 		if (m_scope.lookup(_variableName.name, Scope::Visitor( | ||||
| 			[=](Scope::Variable const& _var) | ||||
| 			{ | ||||
| 				if (int heightDiff = variableHeightDiff(_var, _location, true)) | ||||
| 					m_state.assembly.append(solidity::swapInstruction(heightDiff - 1)); | ||||
| 				m_state.assembly.append(solidity::Instruction::POP); | ||||
| 			}, | ||||
| 			[=](Scope::Label const&) | ||||
| 			{ | ||||
| 				m_state.addError( | ||||
| 					Error::Type::DeclarationError, | ||||
| 					"Label \"" + string(_variableName.name) + "\" used as variable." | ||||
| 				); | ||||
| 			}, | ||||
| 			[=](Scope::Function const&) | ||||
| 			{ | ||||
| 				m_state.addError( | ||||
| 					Error::Type::DeclarationError, | ||||
| 					"Function \"" + string(_variableName.name) + "\" used as variable." | ||||
| 				); | ||||
| 			} | ||||
| 		))) | ||||
| 		auto var = m_scope.lookup(_variableName.name); | ||||
| 		if (var) | ||||
| 		{ | ||||
| 			Scope::Variable const& _var = boost::get<Scope::Variable>(*var); | ||||
| 			if (int heightDiff = variableHeightDiff(_var, _location, true)) | ||||
| 				m_state.assembly.append(solidity::swapInstruction(heightDiff - 1)); | ||||
| 			m_state.assembly.append(solidity::Instruction::POP); | ||||
| 		} | ||||
| 		else if (!m_identifierAccess || !m_identifierAccess(_variableName, m_state.assembly, CodeGenerator::IdentifierContext::LValue)) | ||||
| 			m_state.addError( | ||||
| 				Error::Type::DeclarationError, | ||||
| 				"Identifier \"" + string(_variableName.name) + "\" not found, not unique or not lvalue." | ||||
| 		else | ||||
| 		{ | ||||
| 			solAssert( | ||||
| 				m_identifierAccess.generateCode, | ||||
| 				"Identifier not found and no external access available." | ||||
| 			); | ||||
| 			m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_state.assembly); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Determines the stack height difference to the given variables. Automatically generates
 | ||||
| @ -261,36 +244,33 @@ private: | ||||
| 	/// errors and the (positive) stack height difference otherwise.
 | ||||
| 	int variableHeightDiff(Scope::Variable const& _var, SourceLocation const& _location, bool _forSwap) | ||||
| 	{ | ||||
| 		if (!_var.active) | ||||
| 		{ | ||||
| 			m_state.addError( Error::Type::TypeError, "Variable used before it was declared", _location); | ||||
| 			return 0; | ||||
| 		} | ||||
| 		int heightDiff = m_state.assembly.deposit() - _var.stackHeight; | ||||
| 		if (heightDiff <= (_forSwap ? 1 : 0) || heightDiff > (_forSwap ? 17 : 16)) | ||||
| 		{ | ||||
| 			m_state.addError( | ||||
| 			//@TODO move this to analysis phase.
 | ||||
| 			m_state.errors.push_back(make_shared<Error>( | ||||
| 				Error::Type::TypeError, | ||||
| 				"Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")", | ||||
| 				_location | ||||
| 			); | ||||
| 			)); | ||||
| 			return 0; | ||||
| 		} | ||||
| 		else | ||||
| 			return heightDiff; | ||||
| 	} | ||||
| 
 | ||||
| 	void expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location) | ||||
| 	void expectDeposit(int _deposit, int _oldHeight) | ||||
| 	{ | ||||
| 		if (m_state.assembly.deposit() != _oldHeight + 1) | ||||
| 			m_state.addError(Error::Type::TypeError, | ||||
| 				"Expected instruction(s) to deposit " + | ||||
| 				boost::lexical_cast<string>(_deposit) + | ||||
| 				" item(s) to the stack, but did deposit " + | ||||
| 				boost::lexical_cast<string>(m_state.assembly.deposit() - _oldHeight) + | ||||
| 				" item(s).", | ||||
| 				_location | ||||
| 			); | ||||
| 		solAssert(m_state.assembly.deposit() == _oldHeight + _deposit, "Invalid stack deposit."); | ||||
| 	} | ||||
| 
 | ||||
| 	void checkStackHeight(void const* _astElement) | ||||
| 	{ | ||||
| 		solAssert(m_state.info.stackHeightInfo.count(_astElement), "Stack height for AST element not found."); | ||||
| 		solAssert( | ||||
| 			m_state.info.stackHeightInfo.at(_astElement) == m_state.assembly.deposit() - m_initialDeposit, | ||||
| 			"Stack height mismatch between analysis and code generation phase." | ||||
| 		); | ||||
| 	} | ||||
| 
 | ||||
| 	/// Assigns the label's id to a value taken from eth::Assembly if it has not yet been set.
 | ||||
| @ -305,35 +285,29 @@ private: | ||||
| 
 | ||||
| 	GeneratorState& m_state; | ||||
| 	Scope& m_scope; | ||||
| 	ExternalIdentifierAccess m_identifierAccess; | ||||
| 	int const m_initialDeposit; | ||||
| 	assembly::CodeGenerator::IdentifierAccess m_identifierAccess; | ||||
| }; | ||||
| 
 | ||||
| bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) | ||||
| { | ||||
| 	size_t initialErrorLen = m_errors.size(); | ||||
| 	eth::Assembly assembly; | ||||
| 	GeneratorState state(m_errors, assembly); | ||||
| 	if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData)) | ||||
| 		return false; | ||||
| 	CodeTransform(state, m_parsedData, _identifierAccess); | ||||
| 	return m_errors.size() == initialErrorLen; | ||||
| } | ||||
| 
 | ||||
| eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) | ||||
| eth::Assembly assembly::CodeGenerator::assemble( | ||||
| 	Block const& _parsedData, | ||||
| 	AsmAnalysisInfo& _analysisInfo, | ||||
| 	ExternalIdentifierAccess const& _identifierAccess | ||||
| ) | ||||
| { | ||||
| 	eth::Assembly assembly; | ||||
| 	GeneratorState state(m_errors, assembly); | ||||
| 	if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData)) | ||||
| 		solAssert(false, "Assembly error"); | ||||
| 	CodeTransform(state, m_parsedData, _identifierAccess); | ||||
| 	GeneratorState state(m_errors, _analysisInfo, assembly); | ||||
| 	CodeTransform(state, _parsedData, _identifierAccess); | ||||
| 	return assembly; | ||||
| } | ||||
| 
 | ||||
| void assembly::CodeGenerator::assemble(eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) | ||||
| void assembly::CodeGenerator::assemble( | ||||
| 	Block const& _parsedData, | ||||
| 	AsmAnalysisInfo& _analysisInfo, | ||||
| 	eth::Assembly& _assembly, | ||||
| 	ExternalIdentifierAccess const& _identifierAccess | ||||
| ) | ||||
| { | ||||
| 	GeneratorState state(m_errors, _assembly); | ||||
| 	if (!(AsmAnalyzer(state.scopes, m_errors))(m_parsedData)) | ||||
| 		solAssert(false, "Assembly error"); | ||||
| 	CodeTransform(state, m_parsedData, _identifierAccess); | ||||
| 	GeneratorState state(m_errors, _analysisInfo, _assembly); | ||||
| 	CodeTransform(state, _parsedData, _identifierAccess); | ||||
| } | ||||
|  | ||||
| @ -22,9 +22,11 @@ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <functional> | ||||
| #include <libsolidity/inlineasm/AsmAnalysis.h> | ||||
| #include <libsolidity/interface/Exceptions.h> | ||||
| 
 | ||||
| #include <functional> | ||||
| 
 | ||||
| namespace dev | ||||
| { | ||||
| namespace eth | ||||
| @ -36,30 +38,27 @@ namespace solidity | ||||
| namespace assembly | ||||
| { | ||||
| struct Block; | ||||
| struct Identifier; | ||||
| 
 | ||||
| class CodeGenerator | ||||
| { | ||||
| public: | ||||
| 	enum class IdentifierContext { LValue, RValue }; | ||||
| 	/// Function type that is called for external identifiers. Such a function should search for
 | ||||
| 	/// the identifier and append appropriate assembly items to the assembly. If in lvalue context,
 | ||||
| 	/// the value to assign is assumed to be on the stack and an assignment is to be performed.
 | ||||
| 	/// If in rvalue context, the function is assumed to append instructions to
 | ||||
| 	/// push the value of the identifier onto the stack. On error, the function should return false.
 | ||||
| 	using IdentifierAccess = std::function<bool(assembly::Identifier const&, eth::Assembly&, IdentifierContext)>; | ||||
| 	CodeGenerator(Block const& _parsedData, ErrorList& _errors): | ||||
| 		m_parsedData(_parsedData), m_errors(_errors) {} | ||||
| 	/// Performs type checks and @returns false on error.
 | ||||
| 	/// Actually runs the full code generation but discards the result.
 | ||||
| 	bool typeCheck(IdentifierAccess const& _identifierAccess = IdentifierAccess()); | ||||
| 	CodeGenerator(ErrorList& _errors): | ||||
| 		m_errors(_errors) {} | ||||
| 	/// Performs code generation and @returns the result.
 | ||||
| 	eth::Assembly assemble(IdentifierAccess const& _identifierAccess = IdentifierAccess()); | ||||
| 	eth::Assembly assemble( | ||||
| 		Block const& _parsedData, | ||||
| 		AsmAnalysisInfo& _analysisInfo, | ||||
| 		ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess() | ||||
| 	); | ||||
| 	/// Performs code generation and appends generated to to _assembly.
 | ||||
| 	void assemble(eth::Assembly& _assembly, IdentifierAccess const& _identifierAccess = IdentifierAccess()); | ||||
| 	void assemble( | ||||
| 		Block const& _parsedData, | ||||
| 		AsmAnalysisInfo& _analysisInfo, | ||||
| 		eth::Assembly& _assembly, | ||||
| 		ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess() | ||||
| 	); | ||||
| 
 | ||||
| private: | ||||
| 	Block const& m_parsedData; | ||||
| 	ErrorList& m_errors; | ||||
| }; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										79
									
								
								libsolidity/inlineasm/AsmScope.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								libsolidity/inlineasm/AsmScope.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | ||||
| /*
 | ||||
| 	This file is part of solidity. | ||||
| 
 | ||||
| 	solidity is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 
 | ||||
| 	solidity is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| /**
 | ||||
|  * Scopes for identifiers. | ||||
|  */ | ||||
| 
 | ||||
| #include <libsolidity/inlineasm/AsmScope.h> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace dev::solidity::assembly; | ||||
| 
 | ||||
| 
 | ||||
| bool Scope::registerLabel(string const& _name) | ||||
| { | ||||
| 	if (exists(_name)) | ||||
| 		return false; | ||||
| 	identifiers[_name] = Label(); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool Scope::registerVariable(string const& _name) | ||||
| { | ||||
| 	if (exists(_name)) | ||||
| 		return false; | ||||
| 	identifiers[_name] = Variable(); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool Scope::registerFunction(string const& _name, size_t _arguments, size_t _returns) | ||||
| { | ||||
| 	if (exists(_name)) | ||||
| 		return false; | ||||
| 	identifiers[_name] = Function(_arguments, _returns); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| Scope::Identifier* Scope::lookup(string const& _name) | ||||
| { | ||||
| 	bool crossedFunctionBoundary = false; | ||||
| 	for (Scope* s = this; s; s = s->superScope) | ||||
| 	{ | ||||
| 		auto id = s->identifiers.find(_name); | ||||
| 		if (id != s->identifiers.end()) | ||||
| 		{ | ||||
| 			if (crossedFunctionBoundary && id->second.type() == typeid(Scope::Variable)) | ||||
| 				return nullptr; | ||||
| 			else | ||||
| 				return &id->second; | ||||
| 		} | ||||
| 
 | ||||
| 		if (s->functionScope) | ||||
| 			crossedFunctionBoundary = true; | ||||
| 	} | ||||
| 	return nullptr; | ||||
| } | ||||
| 
 | ||||
| bool Scope::exists(string const& _name) | ||||
| { | ||||
| 	if (identifiers.count(_name)) | ||||
| 		return true; | ||||
| 	else if (superScope) | ||||
| 		return superScope->exists(_name); | ||||
| 	else | ||||
| 		return false; | ||||
| } | ||||
							
								
								
									
										128
									
								
								libsolidity/inlineasm/AsmScope.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								libsolidity/inlineasm/AsmScope.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,128 @@ | ||||
| /*
 | ||||
| 	This file is part of solidity. | ||||
| 
 | ||||
| 	solidity is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 
 | ||||
| 	solidity is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| /**
 | ||||
|  * Scopes for identifiers. | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <libsolidity/interface/Exceptions.h> | ||||
| 
 | ||||
| #include <boost/variant.hpp> | ||||
| 
 | ||||
| #include <functional> | ||||
| #include <memory> | ||||
| 
 | ||||
| namespace dev | ||||
| { | ||||
| namespace solidity | ||||
| { | ||||
| namespace assembly | ||||
| { | ||||
| 
 | ||||
| template <class...> | ||||
| struct GenericVisitor{}; | ||||
| 
 | ||||
| template <class Visitable, class... Others> | ||||
| struct GenericVisitor<Visitable, Others...>: public GenericVisitor<Others...> | ||||
| { | ||||
| 	using GenericVisitor<Others...>::operator (); | ||||
| 	explicit GenericVisitor( | ||||
| 		std::function<void(Visitable&)> _visitor, | ||||
| 		std::function<void(Others&)>... _otherVisitors | ||||
| 	): | ||||
| 		GenericVisitor<Others...>(_otherVisitors...), | ||||
| 		m_visitor(_visitor) | ||||
| 	{} | ||||
| 
 | ||||
| 	void operator()(Visitable& _v) const { m_visitor(_v); } | ||||
| 
 | ||||
| 	std::function<void(Visitable&)> m_visitor; | ||||
| }; | ||||
| template <> | ||||
| struct GenericVisitor<>: public boost::static_visitor<> { | ||||
| 	void operator()() const {} | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| struct Scope | ||||
| { | ||||
| 	struct Variable | ||||
| 	{ | ||||
| 		/// Used during code generation to store the stack height. @todo move there.
 | ||||
| 		int stackHeight = 0; | ||||
| 		/// Used during analysis to check whether we already passed the declaration inside the block.
 | ||||
| 		/// @todo move there.
 | ||||
| 		bool active = false; | ||||
| 	}; | ||||
| 
 | ||||
| 	struct Label | ||||
| 	{ | ||||
| 		size_t id = unassignedLabelId; | ||||
| 		static const size_t errorLabelId = -1; | ||||
| 		static const size_t unassignedLabelId = 0; | ||||
| 	}; | ||||
| 
 | ||||
| 	struct Function | ||||
| 	{ | ||||
| 		Function(size_t _arguments, size_t _returns): arguments(_arguments), returns(_returns) {} | ||||
| 		size_t arguments = 0; | ||||
| 		size_t returns = 0; | ||||
| 	}; | ||||
| 
 | ||||
| 	using Identifier = boost::variant<Variable, Label, Function>; | ||||
| 	using Visitor = GenericVisitor<Variable const, Label const, Function const>; | ||||
| 	using NonconstVisitor = GenericVisitor<Variable, Label, Function>; | ||||
| 
 | ||||
| 	bool registerVariable(std::string const& _name); | ||||
| 	bool registerLabel(std::string const& _name); | ||||
| 	bool registerFunction(std::string const& _name, size_t _arguments, size_t _returns); | ||||
| 
 | ||||
| 	/// Looks up the identifier in this or super scopes and returns a valid pointer if found
 | ||||
| 	/// or a nullptr if not found. Variable lookups up across function boundaries will fail, as
 | ||||
| 	/// will any lookups across assembly boundaries.
 | ||||
| 	/// The pointer will be invalidated if the scope is modified.
 | ||||
| 	/// @param _crossedFunction if true, we already crossed a function boundary during recursive lookup
 | ||||
| 	Identifier* lookup(std::string const& _name); | ||||
| 	/// Looks up the identifier in this and super scopes (will not find variables across function
 | ||||
| 	/// boundaries and generally stops at assembly boundaries) and calls the visitor, returns
 | ||||
| 	/// false if not found.
 | ||||
| 	template <class V> | ||||
| 	bool lookup(std::string const& _name, V const& _visitor) | ||||
| 	{ | ||||
| 		if (Identifier* id = lookup(_name)) | ||||
| 		{ | ||||
| 			boost::apply_visitor(_visitor, *id); | ||||
| 			return true; | ||||
| 		} | ||||
| 		else | ||||
| 			return false; | ||||
| 	} | ||||
| 	/// @returns true if the name exists in this scope or in super scopes (also searches
 | ||||
| 	/// across function and assembly boundaries).
 | ||||
| 	bool exists(std::string const& _name); | ||||
| 
 | ||||
| 	Scope* superScope = nullptr; | ||||
| 	/// If true, variables from the super scope are not visible here (other identifiers are),
 | ||||
| 	/// but they are still taken into account to prevent shadowing.
 | ||||
| 	bool functionScope = false; | ||||
| 	std::map<std::string, Identifier> identifiers; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
| } | ||||
							
								
								
									
										130
									
								
								libsolidity/inlineasm/AsmScopeFiller.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								libsolidity/inlineasm/AsmScopeFiller.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,130 @@ | ||||
| /*
 | ||||
| 	This file is part of solidity. | ||||
| 
 | ||||
| 	solidity is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 
 | ||||
| 	solidity is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| /**
 | ||||
|  * Module responsible for registering identifiers inside their scopes. | ||||
|  */ | ||||
| 
 | ||||
| #include <libsolidity/inlineasm/AsmScopeFiller.h> | ||||
| 
 | ||||
| #include <libsolidity/inlineasm/AsmData.h> | ||||
| #include <libsolidity/inlineasm/AsmScope.h> | ||||
| 
 | ||||
| #include <libsolidity/interface/Exceptions.h> | ||||
| #include <libsolidity/interface/Utils.h> | ||||
| 
 | ||||
| #include <boost/range/adaptor/reversed.hpp> | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <functional> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace dev; | ||||
| using namespace dev::solidity; | ||||
| using namespace dev::solidity::assembly; | ||||
| 
 | ||||
| ScopeFiller::ScopeFiller(ScopeFiller::Scopes& _scopes, ErrorList& _errors): | ||||
| 	m_scopes(_scopes), m_errors(_errors) | ||||
| { | ||||
| 	// Make the Solidity ErrorTag available to inline assembly
 | ||||
| 	Scope::Label errorLabel; | ||||
| 	errorLabel.id = Scope::Label::errorLabelId; | ||||
| 	scope(nullptr).identifiers["invalidJumpLabel"] = errorLabel; | ||||
| 	m_currentScope = &scope(nullptr); | ||||
| } | ||||
| 
 | ||||
| bool ScopeFiller::operator()(Label const& _item) | ||||
| { | ||||
| 	if (!m_currentScope->registerLabel(_item.name)) | ||||
| 	{ | ||||
| 		//@TODO secondary location
 | ||||
| 		m_errors.push_back(make_shared<Error>( | ||||
| 			Error::Type::DeclarationError, | ||||
| 			"Label name " + _item.name + " already taken in this scope.", | ||||
| 			_item.location | ||||
| 		)); | ||||
| 		return false; | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool ScopeFiller::operator()(assembly::VariableDeclaration const& _varDecl) | ||||
| { | ||||
| 	return registerVariable(_varDecl.name, _varDecl.location, *m_currentScope); | ||||
| } | ||||
| 
 | ||||
| bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef) | ||||
| { | ||||
| 	bool success = true; | ||||
| 	if (!m_currentScope->registerFunction(_funDef.name, _funDef.arguments.size(), _funDef.returns.size())) | ||||
| 	{ | ||||
| 		//@TODO secondary location
 | ||||
| 		m_errors.push_back(make_shared<Error>( | ||||
| 			Error::Type::DeclarationError, | ||||
| 			"Function name " + _funDef.name + " already taken in this scope.", | ||||
| 			_funDef.location | ||||
| 		)); | ||||
| 		success = false; | ||||
| 	} | ||||
| 	Scope& body = scope(&_funDef.body); | ||||
| 	body.superScope = m_currentScope; | ||||
| 	body.functionScope = true; | ||||
| 	for (auto const& var: _funDef.arguments + _funDef.returns) | ||||
| 		if (!registerVariable(var, _funDef.location, body)) | ||||
| 			success = false; | ||||
| 
 | ||||
| 	if (!(*this)(_funDef.body)) | ||||
| 		success = false; | ||||
| 
 | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| bool ScopeFiller::operator()(Block const& _block) | ||||
| { | ||||
| 	bool success = true; | ||||
| 	scope(&_block).superScope = m_currentScope; | ||||
| 	m_currentScope = &scope(&_block); | ||||
| 
 | ||||
| 	for (auto const& s: _block.statements) | ||||
| 		if (!boost::apply_visitor(*this, s)) | ||||
| 			success = false; | ||||
| 
 | ||||
| 	m_currentScope = m_currentScope->superScope; | ||||
| 	return success; | ||||
| } | ||||
| 
 | ||||
| bool ScopeFiller::registerVariable(string const& _name, SourceLocation const& _location, Scope& _scope) | ||||
| { | ||||
| 	if (!_scope.registerVariable(_name)) | ||||
| 	{ | ||||
| 		//@TODO secondary location
 | ||||
| 		m_errors.push_back(make_shared<Error>( | ||||
| 			Error::Type::DeclarationError, | ||||
| 			"Variable name " + _name + " already taken in this scope.", | ||||
| 			_location | ||||
| 		)); | ||||
| 		return false; | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| Scope& ScopeFiller::scope(Block const* _block) | ||||
| { | ||||
| 	auto& scope = m_scopes[_block]; | ||||
| 	if (!scope) | ||||
| 		scope = make_shared<Scope>(); | ||||
| 	return *scope; | ||||
| } | ||||
							
								
								
									
										89
									
								
								libsolidity/inlineasm/AsmScopeFiller.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								libsolidity/inlineasm/AsmScopeFiller.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| /*
 | ||||
| 	This file is part of solidity. | ||||
| 
 | ||||
| 	solidity is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 
 | ||||
| 	solidity is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| /**
 | ||||
|  * Module responsible for registering identifiers inside their scopes. | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <libsolidity/interface/Exceptions.h> | ||||
| 
 | ||||
| #include <boost/variant.hpp> | ||||
| 
 | ||||
| #include <functional> | ||||
| #include <memory> | ||||
| 
 | ||||
| namespace dev | ||||
| { | ||||
| namespace solidity | ||||
| { | ||||
| namespace assembly | ||||
| { | ||||
| 
 | ||||
| struct Literal; | ||||
| struct Block; | ||||
| struct Label; | ||||
| struct FunctionalInstruction; | ||||
| struct FunctionalAssignment; | ||||
| struct VariableDeclaration; | ||||
| struct Instruction; | ||||
| struct Identifier; | ||||
| struct Assignment; | ||||
| struct FunctionDefinition; | ||||
| struct FunctionCall; | ||||
| 
 | ||||
| struct Scope; | ||||
| 
 | ||||
| /**
 | ||||
|  * Fills scopes with identifiers and checks for name clashes. | ||||
|  * Does not resolve references. | ||||
|  */ | ||||
| class ScopeFiller: public boost::static_visitor<bool> | ||||
| { | ||||
| public: | ||||
| 	using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>; | ||||
| 	ScopeFiller(Scopes& _scopes, ErrorList& _errors); | ||||
| 
 | ||||
| 	bool operator()(assembly::Instruction const&) { return true; } | ||||
| 	bool operator()(assembly::Literal const&) { return true; } | ||||
| 	bool operator()(assembly::Identifier const&) { return true; } | ||||
| 	bool operator()(assembly::FunctionalInstruction const&) { return true; } | ||||
| 	bool operator()(assembly::Label const& _label); | ||||
| 	bool operator()(assembly::Assignment const&) { return true; } | ||||
| 	bool operator()(assembly::FunctionalAssignment const&) { return true; } | ||||
| 	bool operator()(assembly::VariableDeclaration const& _variableDeclaration); | ||||
| 	bool operator()(assembly::FunctionDefinition const& _functionDefinition); | ||||
| 	bool operator()(assembly::FunctionCall const&) { return true; } | ||||
| 	bool operator()(assembly::Block const& _block); | ||||
| 
 | ||||
| private: | ||||
| 	bool registerVariable( | ||||
| 		std::string const& _name, | ||||
| 		SourceLocation const& _location, | ||||
| 		Scope& _scope | ||||
| 	); | ||||
| 
 | ||||
| 	Scope& scope(assembly::Block const* _block); | ||||
| 
 | ||||
| 	Scope* m_currentScope = nullptr; | ||||
| 	Scopes& m_scopes; | ||||
| 	ErrorList& m_errors; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
| } | ||||
| @ -26,6 +26,7 @@ | ||||
| #include <libsolidity/inlineasm/AsmCodeGen.h> | ||||
| #include <libsolidity/inlineasm/AsmPrinter.h> | ||||
| #include <libsolidity/inlineasm/AsmAnalysis.h> | ||||
| #include <libsolidity/inlineasm/AsmAnalysisInfo.h> | ||||
| 
 | ||||
| #include <libsolidity/parsing/Scanner.h> | ||||
| 
 | ||||
| @ -39,7 +40,10 @@ using namespace dev; | ||||
| using namespace dev::solidity; | ||||
| using namespace dev::solidity::assembly; | ||||
| 
 | ||||
| bool InlineAssemblyStack::parse(shared_ptr<Scanner> const& _scanner) | ||||
| bool InlineAssemblyStack::parse( | ||||
| 	shared_ptr<Scanner> const& _scanner, | ||||
| 	ExternalIdentifierAccess::Resolver const& _resolver | ||||
| ) | ||||
| { | ||||
| 	m_parserResult = make_shared<Block>(); | ||||
| 	Parser parser(m_errors); | ||||
| @ -48,8 +52,8 @@ bool InlineAssemblyStack::parse(shared_ptr<Scanner> const& _scanner) | ||||
| 		return false; | ||||
| 
 | ||||
| 	*m_parserResult = std::move(*result); | ||||
| 	AsmAnalyzer::Scopes scopes; | ||||
| 	return (AsmAnalyzer(scopes, m_errors))(*m_parserResult); | ||||
| 	AsmAnalysisInfo analysisInfo; | ||||
| 	return (AsmAnalyzer(analysisInfo, m_errors, _resolver)).analyze(*m_parserResult); | ||||
| } | ||||
| 
 | ||||
| string InlineAssemblyStack::toString() | ||||
| @ -59,14 +63,17 @@ string InlineAssemblyStack::toString() | ||||
| 
 | ||||
| eth::Assembly InlineAssemblyStack::assemble() | ||||
| { | ||||
| 	CodeGenerator codeGen(*m_parserResult, m_errors); | ||||
| 	return codeGen.assemble(); | ||||
| 	AsmAnalysisInfo analysisInfo; | ||||
| 	AsmAnalyzer analyzer(analysisInfo, m_errors); | ||||
| 	solAssert(analyzer.analyze(*m_parserResult), ""); | ||||
| 	CodeGenerator codeGen(m_errors); | ||||
| 	return codeGen.assemble(*m_parserResult, analysisInfo); | ||||
| } | ||||
| 
 | ||||
| bool InlineAssemblyStack::parseAndAssemble( | ||||
| 	string const& _input, | ||||
| 	eth::Assembly& _assembly, | ||||
| 	CodeGenerator::IdentifierAccess const& _identifierAccess | ||||
| 	ExternalIdentifierAccess const& _identifierAccess | ||||
| ) | ||||
| { | ||||
| 	ErrorList errors; | ||||
| @ -74,8 +81,12 @@ bool InlineAssemblyStack::parseAndAssemble( | ||||
| 	auto parserResult = Parser(errors).parse(scanner); | ||||
| 	if (!errors.empty()) | ||||
| 		return false; | ||||
| 	solAssert(parserResult, ""); | ||||
| 
 | ||||
| 	CodeGenerator(*parserResult, errors).assemble(_assembly, _identifierAccess); | ||||
| 	AsmAnalysisInfo analysisInfo; | ||||
| 	AsmAnalyzer analyzer(analysisInfo, errors, _identifierAccess.resolve); | ||||
| 	solAssert(analyzer.analyze(*parserResult), ""); | ||||
| 	CodeGenerator(errors).assemble(*parserResult, analysisInfo, _assembly, _identifierAccess); | ||||
| 
 | ||||
| 	// At this point, the assembly might be messed up, but we should throw an
 | ||||
| 	// internal compiler error anyway.
 | ||||
|  | ||||
| @ -22,10 +22,10 @@ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <libsolidity/interface/Exceptions.h> | ||||
| 
 | ||||
| #include <string> | ||||
| #include <functional> | ||||
| #include <libsolidity/interface/Exceptions.h> | ||||
| #include <libsolidity/inlineasm/AsmCodeGen.h> | ||||
| 
 | ||||
| namespace dev | ||||
| { | ||||
| @ -39,13 +39,34 @@ class Scanner; | ||||
| namespace assembly | ||||
| { | ||||
| struct Block; | ||||
| struct Identifier; | ||||
| 
 | ||||
| enum class IdentifierContext { LValue, RValue }; | ||||
| 
 | ||||
| /// Object that is used to resolve references and generate code for access to identifiers external
 | ||||
| /// to inline assembly (not used in standalone assembly mode).
 | ||||
| struct ExternalIdentifierAccess | ||||
| { | ||||
| 	using Resolver = std::function<size_t(assembly::Identifier const&, IdentifierContext)>; | ||||
| 	/// Resolve a an external reference given by the identifier in the given context.
 | ||||
| 	/// @returns the size of the value (number of stack slots) or size_t(-1) if not found.
 | ||||
| 	Resolver resolve; | ||||
| 	using CodeGenerator = std::function<void(assembly::Identifier const&, IdentifierContext, eth::Assembly&)>; | ||||
| 	/// Generate code for retrieving the value (rvalue context) or storing the value (lvalue context)
 | ||||
| 	/// of an identifier. The code should be appended to the assembly. In rvalue context, the value is supposed
 | ||||
| 	/// to be put onto the stack, in lvalue context, the value is assumed to be at the top of the stack.
 | ||||
| 	CodeGenerator generateCode; | ||||
| }; | ||||
| 
 | ||||
| class InlineAssemblyStack | ||||
| { | ||||
| public: | ||||
| 	/// Parse the given inline assembly chunk starting with `{` and ending with the corresponding `}`.
 | ||||
| 	/// @return false or error.
 | ||||
| 	bool parse(std::shared_ptr<Scanner> const& _scanner); | ||||
| 	bool parse( | ||||
| 		std::shared_ptr<Scanner> const& _scanner, | ||||
| 		ExternalIdentifierAccess::Resolver const& _externalIdentifierResolver = ExternalIdentifierAccess::Resolver() | ||||
| 	); | ||||
| 	/// Converts the parser result back into a string form (not necessarily the same form
 | ||||
| 	/// as the source form, but it should parse into the same parsed form again).
 | ||||
| 	std::string toString(); | ||||
| @ -56,7 +77,7 @@ public: | ||||
| 	bool parseAndAssemble( | ||||
| 		std::string const& _input, | ||||
| 		eth::Assembly& _assembly, | ||||
| 		CodeGenerator::IdentifierAccess const& _identifierAccess = CodeGenerator::IdentifierAccess() | ||||
| 		ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess() | ||||
| 	); | ||||
| 
 | ||||
| 	ErrorList const& errors() const { return m_errors; } | ||||
|  | ||||
| @ -30,6 +30,7 @@ | ||||
| #include <libevmasm/Assembly.h> | ||||
| 
 | ||||
| #include <boost/optional.hpp> | ||||
| #include <boost/algorithm/string/replace.hpp> | ||||
| 
 | ||||
| #include <string> | ||||
| #include <memory> | ||||
| @ -63,7 +64,7 @@ boost::optional<Error> parseAndReturnFirstError(string const& _source, bool _ass | ||||
| 	} | ||||
| 	if (!success) | ||||
| 	{ | ||||
| 		BOOST_CHECK_EQUAL(stack.errors().size(), 1); | ||||
| 		BOOST_REQUIRE_EQUAL(stack.errors().size(), 1); | ||||
| 		return *stack.errors().front(); | ||||
| 	} | ||||
| 	else | ||||
| @ -137,22 +138,22 @@ BOOST_AUTO_TEST_CASE(smoke_test) | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(simple_instructions) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ dup1 dup1 mul dup1 sub }")); | ||||
| 	BOOST_CHECK(successParse("{ dup1 dup1 mul dup1 sub pop }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(suicide_selfdestruct) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ suicide selfdestruct }")); | ||||
| 	BOOST_CHECK(successParse("{ 0x01 suicide 0x02 selfdestruct }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(keywords) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ byte return address }")); | ||||
| 	BOOST_CHECK(successParse("{ 1 2 byte 2 return address pop }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(constants) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ 7 8 mul }")); | ||||
| 	BOOST_CHECK(successParse("{ 7 8 mul pop }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(vardecl) | ||||
| @ -162,37 +163,43 @@ BOOST_AUTO_TEST_CASE(vardecl) | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(assignment) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ 7 8 add =: x }")); | ||||
| 	BOOST_CHECK(successParse("{ let x := 2 7 8 add =: x }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(label) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ 7 abc: 8 eq abc jump }")); | ||||
| 	BOOST_CHECK(successParse("{ 7 abc: 8 eq abc jump pop }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(label_complex) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) }")); | ||||
| 	BOOST_CHECK(successParse("{ 7 abc: 8 eq jump(abc) jumpi(eq(7, 8), abc) pop }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(functional) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ add(7, mul(6, x)) add mul(7, 8) }")); | ||||
| 	BOOST_CHECK(successParse("{ let x := 2 add(7, mul(6, x)) mul(7, 8) add =: x }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(functional_assignment) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ x := 7 }")); | ||||
| 	BOOST_CHECK(successParse("{ let x := 2 x := 7 }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(functional_assignment_complex) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ x := add(7, mul(6, x)) add mul(7, 8) }")); | ||||
| 	BOOST_CHECK(successParse("{ let x := 2 x := add(7, mul(6, x)) mul(7, 8) add }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(vardecl_complex) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ let x := add(7, mul(6, x)) add mul(7, 8) }")); | ||||
| 	BOOST_CHECK(successParse("{ let y := 2 let x := add(7, mul(6, y)) add mul(7, 8) }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(variable_use_before_decl) | ||||
| { | ||||
| 	CHECK_PARSE_ERROR("{ x := 2 let x := 3 }", DeclarationError, "Variable x used before it was declared."); | ||||
| 	CHECK_PARSE_ERROR("{ let x := mul(2, x) }", DeclarationError, "Variable x used before it was declared."); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(blocks) | ||||
| @ -212,7 +219,28 @@ BOOST_AUTO_TEST_CASE(function_definitions_multiple_args) | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(function_calls) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ g(1, 2, f(mul(2, 3))) x() }")); | ||||
| 	BOOST_CHECK(successParse("{ function f(a) -> (b) {} function g(a, b, c) {} function x() { g(1, 2, f(mul(2, 3))) x() } }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(opcode_for_functions) | ||||
| { | ||||
| 	CHECK_PARSE_ERROR("{ function gas() { } }", ParserError, "Cannot use instruction names for identifier names."); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(opcode_for_function_args) | ||||
| { | ||||
| 	CHECK_PARSE_ERROR("{ function f(gas) { } }", ParserError, "Cannot use instruction names for identifier names."); | ||||
| 	CHECK_PARSE_ERROR("{ function f() -> (gas) { } }", ParserError, "Cannot use instruction names for identifier names."); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(name_clashes) | ||||
| { | ||||
| 	CHECK_PARSE_ERROR("{ let g := 2 function g() { } }", DeclarationError, "Function name g already taken in this scope"); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(variable_access_cross_functions) | ||||
| { | ||||
| 	CHECK_PARSE_ERROR("{ let x := 2 function g() { x pop } }", DeclarationError, "Identifier not found."); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_SUITE_END() | ||||
| @ -226,7 +254,7 @@ BOOST_AUTO_TEST_CASE(print_smoke) | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(print_instructions) | ||||
| { | ||||
| 	parsePrintCompare("{\n    7\n    8\n    mul\n    dup10\n    add\n}"); | ||||
| 	parsePrintCompare("{\n    7\n    8\n    mul\n    dup10\n    add\n    pop\n}"); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(print_subblock) | ||||
| @ -236,7 +264,7 @@ BOOST_AUTO_TEST_CASE(print_subblock) | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(print_functional) | ||||
| { | ||||
| 	parsePrintCompare("{\n    mul(sload(0x12), 7)\n}"); | ||||
| 	parsePrintCompare("{\n    let x := mul(sload(0x12), 7)\n}"); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(print_label) | ||||
| @ -251,13 +279,13 @@ BOOST_AUTO_TEST_CASE(print_assignments) | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(print_string_literals) | ||||
| { | ||||
| 	parsePrintCompare("{\n    \"\\n'\\xab\\x95\\\"\"\n}"); | ||||
| 	parsePrintCompare("{\n    \"\\n'\\xab\\x95\\\"\"\n    pop\n}"); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(print_string_literal_unicode) | ||||
| { | ||||
| 	string source = "{ \"\\u1bac\" }"; | ||||
| 	string parsed = "{\n    \"\\xe1\\xae\\xac\"\n}"; | ||||
| 	string source = "{ let x := \"\\u1bac\" }"; | ||||
| 	string parsed = "{\n    let x := \"\\xe1\\xae\\xac\"\n}"; | ||||
| 	assembly::InlineAssemblyStack stack; | ||||
| 	BOOST_REQUIRE(stack.parse(std::make_shared<Scanner>(CharStream(source)))); | ||||
| 	BOOST_REQUIRE(stack.errors().empty()); | ||||
| @ -272,7 +300,21 @@ BOOST_AUTO_TEST_CASE(function_definitions_multiple_args) | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(function_calls) | ||||
| { | ||||
| 	parsePrintCompare("{\n    g(1, mul(2, x), f(mul(2, 3)))\n    x()\n}"); | ||||
| 	string source = R"({ | ||||
| 	function y() | ||||
| 	{ | ||||
| 	} | ||||
| 	function f(a) -> (b) | ||||
| 	{ | ||||
| 	} | ||||
| 	function g(a, b, c) | ||||
| 	{ | ||||
| 	} | ||||
| 	g(1, mul(2, address), f(mul(2, caller))) | ||||
| 	y() | ||||
| })"; | ||||
| 	boost::replace_all(source, "\t", "    "); | ||||
| 	parsePrintCompare(source); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_SUITE_END() | ||||
| @ -291,27 +333,32 @@ BOOST_AUTO_TEST_CASE(oversize_string_literals) | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(assignment_after_tag) | ||||
| { | ||||
| 	BOOST_CHECK(successParse("{ let x := 1 { tag: =: x } }")); | ||||
| 	BOOST_CHECK(successParse("{ let x := 1 { 7 tag: =: x } }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(magic_variables) | ||||
| { | ||||
| 	CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found or not unique"); | ||||
| 	CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found or not unique"); | ||||
| 	BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover }")); | ||||
| 	CHECK_ASSEMBLE_ERROR("{ this pop }", DeclarationError, "Identifier not found"); | ||||
| 	CHECK_ASSEMBLE_ERROR("{ ecrecover pop }", DeclarationError, "Identifier not found"); | ||||
| 	BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover pop }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(stack_variables) | ||||
| { | ||||
| 	BOOST_CHECK(successAssemble("{ let y := 3 { 2 { let x := y } pop} }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(imbalanced_stack) | ||||
| { | ||||
| 	BOOST_CHECK(successAssemble("{ 1 2 mul pop }", false)); | ||||
| 	CHECK_ASSEMBLE_ERROR("{ 1 }", Warning, "Inline assembly block is not balanced. It leaves"); | ||||
| 	CHECK_ASSEMBLE_ERROR("{ pop }", Warning, "Inline assembly block is not balanced. It takes"); | ||||
| 	CHECK_ASSEMBLE_ERROR("{ 1 }", DeclarationError, "Unbalanced stack at the end of a block: 1 surplus item(s)."); | ||||
| 	CHECK_ASSEMBLE_ERROR("{ pop }", DeclarationError, "Unbalanced stack at the end of a block: 1 missing item(s)."); | ||||
| 	BOOST_CHECK(successAssemble("{ let x := 4 7 add }", false)); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(error_tag) | ||||
| { | ||||
| 	BOOST_CHECK(successAssemble("{ invalidJumpLabel }")); | ||||
| 	BOOST_CHECK(successAssemble("{ jump(invalidJumpLabel) }")); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(designated_invalid_instruction) | ||||
|  | ||||
| @ -7426,18 +7426,52 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_access) | ||||
| 			uint16 x; | ||||
| 			uint16 public y; | ||||
| 			uint public z; | ||||
| 			function f() { | ||||
| 				// we know that z is aligned because it is too large, so we just discard its
 | ||||
| 				// intra-slot offset value
 | ||||
| 				assembly { 7 z pop sstore } | ||||
| 			function f() returns (bool) { | ||||
| 				uint off1; | ||||
| 				uint off2; | ||||
| 				assembly { | ||||
| 					sstore(z_slot, 7) | ||||
| 					off1 := z_offset | ||||
| 					off2 := y_offset | ||||
| 				} | ||||
| 				assert(off1 == 0); | ||||
| 				assert(off2 == 2); | ||||
| 				return true; | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	compileAndRun(sourceCode, 0, "C"); | ||||
| 	BOOST_CHECK(callContractFunction("f()") == encodeArgs()); | ||||
| 	BOOST_CHECK(callContractFunction("f()") == encodeArgs(true)); | ||||
| 	BOOST_CHECK(callContractFunction("z()") == encodeArgs(u256(7))); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(inline_assembly_storage_access_via_pointer) | ||||
| { | ||||
| 	char const* sourceCode = R"( | ||||
| 		contract C { | ||||
| 			struct Data { uint contents; } | ||||
| 			uint public separator; | ||||
| 			Data public a; | ||||
| 			uint public separator2; | ||||
| 			function f() returns (bool) { | ||||
| 				Data x = a; | ||||
| 				uint off; | ||||
| 				assembly { | ||||
| 					sstore(x_slot, 7) | ||||
| 					off := x_offset | ||||
| 				} | ||||
| 				assert(off == 0); | ||||
| 				return true; | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	compileAndRun(sourceCode, 0, "C"); | ||||
| 	BOOST_CHECK(callContractFunction("f()") == encodeArgs(true)); | ||||
| 	BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(7))); | ||||
| 	BOOST_CHECK(callContractFunction("separator()") == encodeArgs(u256(0))); | ||||
| 	BOOST_CHECK(callContractFunction("separator2()") == encodeArgs(u256(0))); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(inline_assembly_jumps) | ||||
| { | ||||
| 	char const* sourceCode = R"( | ||||
| @ -7474,6 +7508,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_access) | ||||
| 				assembly { | ||||
| 					_x | ||||
| 					jump(g) | ||||
| 					pop | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @ -4997,7 +4997,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_positive_stack) | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_WARNING(text, "Inline assembly block is not balanced"); | ||||
| 	CHECK_ERROR(text, DeclarationError, "Unbalanced stack at the end of a block: 1 surplus item(s)."); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack) | ||||
| @ -5011,7 +5011,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack) | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_WARNING(text, "Inline assembly block is not balanced"); | ||||
| 	CHECK_ERROR(text, DeclarationError, "Unbalanced stack at the end of a block: 1 missing item(s)."); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load) | ||||
| @ -5024,7 +5024,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load) | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_WARNING(text, "Inline assembly block is not balanced"); | ||||
| 	CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,"); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(inline_assembly_in_modifier) | ||||
| @ -5053,12 +5053,11 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage) | ||||
| 			function f() { | ||||
| 				assembly { | ||||
| 					x := 2 | ||||
| 					pop | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_ERROR(text, DeclarationError, "not found, not unique or not lvalue."); | ||||
| 	CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,"); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers) | ||||
| @ -5069,7 +5068,6 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers) | ||||
| 			modifier m { | ||||
| 				assembly { | ||||
| 					x := 2 | ||||
| 					pop | ||||
| 				} | ||||
| 				_; | ||||
| 			} | ||||
| @ -5077,7 +5075,37 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers) | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_ERROR(text, DeclarationError, ""); | ||||
| 	CHECK_ERROR(text, TypeError, "Only local variables are supported. To access storage variables,"); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(inline_assembly_constant_assign) | ||||
| { | ||||
| 	char const* text = R"( | ||||
| 		contract test { | ||||
| 			uint constant x = 1; | ||||
| 			function f() { | ||||
| 				assembly { | ||||
| 					x := 2 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_ERROR(text, TypeError, "Constant variables not supported by inline assembly"); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(inline_assembly_constant_access) | ||||
| { | ||||
| 	char const* text = R"( | ||||
| 		contract test { | ||||
| 			uint constant x = 1; | ||||
| 			function f() { | ||||
| 				assembly { | ||||
| 					let y := x | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_ERROR(text, TypeError, "Constant variables not supported by inline assembly"); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(invalid_mobile_type) | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user