mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Refactor AnalysisFramework to share more features between test cases that depend on it
This commit is contained in:
		
							parent
							
								
									dc44f8ad91
								
							
						
					
					
						commit
						309a8de530
					
				| @ -22,6 +22,7 @@ | ||||
| #include <test/libsolidity/AnalysisFramework.h> | ||||
| 
 | ||||
| #include <test/libsolidity/util/Common.h> | ||||
| #include <test/libsolidity/util/SoltestErrors.h> | ||||
| #include <test/Common.h> | ||||
| 
 | ||||
| #include <libsolidity/interface/CompilerStack.h> | ||||
| @ -66,11 +67,60 @@ AnalysisFramework::parseAnalyseAndReturnError( | ||||
| 	return make_pair(&compiler().ast(""), std::move(errors)); | ||||
| } | ||||
| 
 | ||||
| bool AnalysisFramework::runFramework(StringMap _sources, PipelineStage _targetStage) | ||||
| { | ||||
| 	resetFramework(); | ||||
| 	m_targetStage = _targetStage; | ||||
| 	soltestAssert(m_compiler); | ||||
| 
 | ||||
| 	m_compiler->setSources(std::move(_sources)); | ||||
| 	setupCompiler(*m_compiler); | ||||
| 	executeCompilationPipeline(); | ||||
| 	return pipelineSuccessful(); | ||||
| } | ||||
| 
 | ||||
| void AnalysisFramework::resetFramework() | ||||
| { | ||||
| 	compiler().reset(); | ||||
| 	m_targetStage = PipelineStage::Compilation; | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<CompilerStack> AnalysisFramework::createStack() const | ||||
| { | ||||
| 	return std::make_unique<CompilerStack>(); | ||||
| } | ||||
| 
 | ||||
| void AnalysisFramework::setupCompiler(CompilerStack& _compiler) | ||||
| { | ||||
| 	_compiler.setEVMVersion(solidity::test::CommonOptions::get().evmVersion()); | ||||
| } | ||||
| 
 | ||||
| void AnalysisFramework::executeCompilationPipeline() | ||||
| { | ||||
| 	soltestAssert(m_compiler); | ||||
| 
 | ||||
| 	// If you add a new stage, remember to handle it below.
 | ||||
| 	soltestAssert( | ||||
| 		m_targetStage == PipelineStage::Parsing || | ||||
| 		m_targetStage == PipelineStage::Analysis || | ||||
| 		m_targetStage == PipelineStage::Compilation | ||||
| 	); | ||||
| 
 | ||||
| 	bool parsingSuccessful = m_compiler->parse(); | ||||
| 	soltestAssert(parsingSuccessful || !filteredErrors(false /* _includeWarningsAndInfos */).empty()); | ||||
| 	if (!parsingSuccessful || stageSuccessful(m_targetStage)) | ||||
| 		return; | ||||
| 
 | ||||
| 	bool analysisSuccessful = m_compiler->analyze(); | ||||
| 	soltestAssert(analysisSuccessful || !filteredErrors(false /* _includeWarningsAndInfos */).empty()); | ||||
| 	if (!analysisSuccessful || stageSuccessful(m_targetStage)) | ||||
| 		return; | ||||
| 
 | ||||
| 	bool compilationSuccessful = m_compiler->compile(); | ||||
| 	soltestAssert(compilationSuccessful || !filteredErrors(false /* _includeWarningsAndInfos */).empty()); | ||||
| 	soltestAssert(stageSuccessful(m_targetStage) == compilationSuccessful); | ||||
| } | ||||
| 
 | ||||
| ErrorList AnalysisFramework::filterErrors(ErrorList const& _errorList, bool _includeWarningsAndInfos) const | ||||
| { | ||||
| 	ErrorList errors; | ||||
| @ -113,6 +163,16 @@ ErrorList AnalysisFramework::filterErrors(ErrorList const& _errorList, bool _inc | ||||
| 	return errors; | ||||
| } | ||||
| 
 | ||||
| bool AnalysisFramework::stageSuccessful(PipelineStage _stage) const | ||||
| { | ||||
| 	switch (_stage) { | ||||
| 		case PipelineStage::Parsing: return compiler().state() >= CompilerStack::Parsed; | ||||
| 		case PipelineStage::Analysis: return compiler().state() >= CompilerStack::AnalysisSuccessful; | ||||
| 		case PipelineStage::Compilation: return compiler().state() >= CompilerStack::CompilationSuccessful; | ||||
| 	} | ||||
| 	unreachable(); | ||||
| } | ||||
| 
 | ||||
| SourceUnit const* AnalysisFramework::parseAndAnalyse(std::string const& _source) | ||||
| { | ||||
| 	auto sourceAndError = parseAnalyseAndReturnError(_source); | ||||
|  | ||||
| @ -39,6 +39,12 @@ using FunctionTypePointer = FunctionType const*; | ||||
| namespace solidity::frontend::test | ||||
| { | ||||
| 
 | ||||
| enum class PipelineStage { | ||||
| 	Parsing, | ||||
| 	Analysis, | ||||
| 	Compilation, | ||||
| }; | ||||
| 
 | ||||
| class AnalysisFramework | ||||
| { | ||||
| 
 | ||||
| @ -56,6 +62,25 @@ protected: | ||||
| 	bool success(std::string const& _source); | ||||
| 	langutil::ErrorList expectError(std::string const& _source, bool _warning = false, bool _allowMultiple = false); | ||||
| 
 | ||||
| public: | ||||
| 	/// Runs the full compiler pipeline on specified sources. This is the main function of the
 | ||||
| 	/// framework. Resets the stack, configures it and runs either until the first failed stage or
 | ||||
| 	/// until the @p _targetStage is reached.
 | ||||
| 	/// Afterwards the caller can inspect the stack via @p compiler(). The framework provides a few
 | ||||
| 	/// convenience helpers to check the state and error list, in general the caller can freely
 | ||||
| 	/// access the stack, including generating outputs if the compilation succeeded.
 | ||||
| 	bool runFramework(StringMap _sources, PipelineStage _targetStage = PipelineStage::Compilation); | ||||
| 	bool runFramework(std::string _source, PipelineStage _targetStage = PipelineStage::Compilation) | ||||
| 	{ | ||||
| 		return runFramework({{"", std::move(_source)}}, _targetStage); | ||||
| 	} | ||||
| 
 | ||||
| 	void resetFramework(); | ||||
| 
 | ||||
| 	PipelineStage targetStage() const { return m_targetStage; } | ||||
| 	bool pipelineSuccessful() const { return stageSuccessful(m_targetStage); } | ||||
| 	bool stageSuccessful(PipelineStage _stage) const; | ||||
| 
 | ||||
| 	std::string formatErrors( | ||||
| 		langutil::ErrorList const& _errors, | ||||
| 		bool _colored = false, | ||||
| @ -80,9 +105,6 @@ protected: | ||||
| 		return filterErrors(compiler().errors(), _includeWarningsAndInfos); | ||||
| 	} | ||||
| 
 | ||||
| 	std::vector<std::string> m_warningsToFilter = {"This is a pre-release compiler version"}; | ||||
| 	std::vector<std::string> m_messagesToCut = {"Source file requires different compiler version (current compiler is"}; | ||||
| 
 | ||||
| 	/// @returns reference to lazy-instantiated CompilerStack.
 | ||||
| 	solidity::frontend::CompilerStack& compiler() | ||||
| 	{ | ||||
| @ -99,12 +121,25 @@ protected: | ||||
| 		return *m_compiler; | ||||
| 	} | ||||
| 
 | ||||
| protected: | ||||
| 	/// Creates a new instance of @p CompilerStack. Override if your test case needs to pass in
 | ||||
| 	/// custom constructor arguments.
 | ||||
| 	virtual std::unique_ptr<CompilerStack> createStack() const; | ||||
| 
 | ||||
| 	/// Configures @p CompilerStack. The default implementation sets basic parameters based on
 | ||||
| 	/// CLI options. Override if your test case needs extra configuration.
 | ||||
| 	virtual void setupCompiler(CompilerStack& _compiler); | ||||
| 
 | ||||
| 	/// Executes the requested pipeline stages until @p m_targetStage is reached.
 | ||||
| 	/// Stops at the first failed stage.
 | ||||
| 	void executeCompilationPipeline(); | ||||
| 
 | ||||
| 	std::vector<std::string> m_warningsToFilter = {"This is a pre-release compiler version"}; | ||||
| 	std::vector<std::string> m_messagesToCut = {"Source file requires different compiler version (current compiler is"}; | ||||
| 
 | ||||
| private: | ||||
| 	mutable std::unique_ptr<solidity::frontend::CompilerStack> m_compiler; | ||||
| 	PipelineStage m_targetStage = PipelineStage::Compilation; | ||||
| }; | ||||
| 
 | ||||
| // Asserts that the compilation down to typechecking
 | ||||
|  | ||||
| @ -97,13 +97,13 @@ void GasTest::printUpdatedExpectations(std::ostream& _stream, std::string const& | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| TestCase::TestResult GasTest::run(std::ostream& _stream, std::string const& _linePrefix, bool _formatted) | ||||
| void GasTest::setupCompiler(CompilerStack& _compiler) | ||||
| { | ||||
| 	compiler().reset(); | ||||
| 
 | ||||
| 	// Prerelease CBOR metadata varies in size due to changing version numbers and build dates.
 | ||||
| 	// This leads to volatile creation cost estimates. Therefore we force the compiler to
 | ||||
| 	// release mode for testing gas estimates.
 | ||||
| 	compiler().setMetadataFormat(CompilerStack::MetadataFormat::NoMetadata); | ||||
| 	_compiler.setMetadataFormat(CompilerStack::MetadataFormat::NoMetadata); | ||||
| 	OptimiserSettings settings = m_optimise ? OptimiserSettings::standard() : OptimiserSettings::minimal(); | ||||
| 	if (m_optimiseYul) | ||||
| 	{ | ||||
| @ -111,10 +111,12 @@ TestCase::TestResult GasTest::run(std::ostream& _stream, std::string const& _lin | ||||
| 		settings.optimizeStackAllocation = m_optimise; | ||||
| 	} | ||||
| 	settings.expectedExecutionsPerDeployment = m_optimiseRuns; | ||||
| 	compiler().setOptimiserSettings(settings); | ||||
| 	compiler().setSources({{"", withPreamble(m_source)}}); | ||||
| 	_compiler.setOptimiserSettings(settings); | ||||
| } | ||||
| 
 | ||||
| 	if (!compiler().parseAndAnalyze() || !compiler().compile()) | ||||
| TestCase::TestResult GasTest::run(std::ostream& _stream, std::string const& _linePrefix, bool _formatted) | ||||
| { | ||||
| 	if (!runFramework(withPreamble(m_source), PipelineStage::Compilation)) | ||||
| 	{ | ||||
| 		_stream << formatErrors(filteredErrors(), _formatted); | ||||
| 		return TestResult::FatalError; | ||||
|  | ||||
| @ -44,6 +44,9 @@ public: | ||||
| 	void printSource(std::ostream &_stream, std::string const &_linePrefix = "", bool _formatted = false) const override; | ||||
| 	void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override; | ||||
| 
 | ||||
| protected: | ||||
| 	void setupCompiler(CompilerStack& _compiler) override; | ||||
| 
 | ||||
| private: | ||||
| 	void parseExpectations(std::istream& _stream); | ||||
| 
 | ||||
|  | ||||
| @ -36,13 +36,15 @@ using namespace solidity::frontend; | ||||
| using namespace solidity::frontend::test; | ||||
| using namespace yul; | ||||
| 
 | ||||
| void MemoryGuardTest::setupCompiler(CompilerStack& _compiler) | ||||
| { | ||||
| 	_compiler.setViaIR(true); | ||||
| 	_compiler.setOptimiserSettings(OptimiserSettings::none()); | ||||
| } | ||||
| 
 | ||||
| TestCase::TestResult MemoryGuardTest::run(std::ostream& _stream, std::string const& _linePrefix, bool _formatted) | ||||
| { | ||||
| 	compiler().reset(); | ||||
| 	compiler().setSources(StringMap{{"", m_source}}); | ||||
| 	compiler().setViaIR(true); | ||||
| 	compiler().setOptimiserSettings(OptimiserSettings::none()); | ||||
| 	if (!compiler().compile()) | ||||
| 	if (!runFramework(m_source, PipelineStage::Compilation)) | ||||
| 	{ | ||||
| 		_stream << formatErrors(filteredErrors(), _formatted); | ||||
| 		return TestResult::FatalError; | ||||
|  | ||||
| @ -48,6 +48,9 @@ public: | ||||
| 	} | ||||
| 
 | ||||
| 	TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override; | ||||
| 
 | ||||
| protected: | ||||
| 	void setupCompiler(CompilerStack& _compiler) override; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -126,11 +126,11 @@ SMTCheckerTest::SMTCheckerTest(std::string const& _filename): SyntaxTest(_filena | ||||
| 	m_modelCheckerSettings.bmcLoopIterations = std::optional<unsigned>{bmcLoopIterations}; | ||||
| } | ||||
| 
 | ||||
| void SMTCheckerTest::setupCompiler() | ||||
| void SMTCheckerTest::setupCompiler(CompilerStack& _compiler) | ||||
| { | ||||
| 	SyntaxTest::setupCompiler(); | ||||
| 	SyntaxTest::setupCompiler(_compiler); | ||||
| 
 | ||||
| 	compiler().setModelCheckerSettings(m_modelCheckerSettings); | ||||
| 	_compiler.setModelCheckerSettings(m_modelCheckerSettings); | ||||
| } | ||||
| 
 | ||||
| void SMTCheckerTest::filterObtainedErrors() | ||||
|  | ||||
| @ -38,7 +38,7 @@ public: | ||||
| 	} | ||||
| 	SMTCheckerTest(std::string const& _filename); | ||||
| 
 | ||||
| 	void setupCompiler() override; | ||||
| 	void setupCompiler(CompilerStack& _compiler) override; | ||||
| 	void filterObtainedErrors() override; | ||||
| 
 | ||||
| 	void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override; | ||||
|  | ||||
| @ -28,7 +28,17 @@ | ||||
| namespace solidity::frontend::test | ||||
| { | ||||
| 
 | ||||
| BOOST_FIXTURE_TEST_SUITE(SolidityCompiler, AnalysisFramework) | ||||
| class SolidityCompilerFixture: protected AnalysisFramework | ||||
| { | ||||
| 	void setupCompiler(CompilerStack& _compiler) override | ||||
| 	{ | ||||
| 		// FIXME: This test was probably supposed to respect CommonOptions::get().optimize but
 | ||||
| 		// due to a bug it was always running with optimizer disabled and it does not pass with it.
 | ||||
| 		_compiler.setOptimiserSettings(false); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| BOOST_FIXTURE_TEST_SUITE(SolidityCompiler, SolidityCompilerFixture) | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(does_not_include_creation_time_only_internal_functions) | ||||
| { | ||||
| @ -39,12 +49,13 @@ BOOST_AUTO_TEST_CASE(does_not_include_creation_time_only_internal_functions) | ||||
| 			function f() internal { unchecked { for (uint i = 0; i < 10; ++i) x += 3 + i; } } | ||||
| 		} | ||||
| 	)"; | ||||
| 	compiler().setOptimiserSettings(solidity::test::CommonOptions::get().optimize); | ||||
| 	BOOST_REQUIRE(success(sourceCode)); | ||||
| 
 | ||||
| 	runFramework(sourceCode, PipelineStage::Compilation); | ||||
| 	BOOST_REQUIRE_MESSAGE( | ||||
| 		compiler().compile(), | ||||
| 		pipelineSuccessful(), | ||||
| 		"Contract compilation failed:\n" + formatErrors(filteredErrors(), true /* _colored */) | ||||
| 	); | ||||
| 
 | ||||
| 	bytes const& creationBytecode = solidity::test::bytecodeSansMetadata(compiler().object("C").bytecode); | ||||
| 	bytes const& runtimeBytecode = solidity::test::bytecodeSansMetadata(compiler().runtimeObject("C").bytecode); | ||||
| 	BOOST_CHECK(creationBytecode.size() >= 90); | ||||
|  | ||||
| @ -49,53 +49,51 @@ SyntaxTest::SyntaxTest( | ||||
| 	m_optimiseYul = m_reader.boolSetting("optimize-yul", true); | ||||
| } | ||||
| 
 | ||||
| void SyntaxTest::setupCompiler() | ||||
| void SyntaxTest::setupCompiler(CompilerStack& _compiler) | ||||
| { | ||||
| 	compiler().reset(); | ||||
| 	compiler().setSources(withPreamble(m_sources.sources)); | ||||
| 	compiler().setEVMVersion(m_evmVersion); | ||||
| 	compiler().setOptimiserSettings( | ||||
| 	AnalysisFramework::setupCompiler(_compiler); | ||||
| 
 | ||||
| 	_compiler.setEVMVersion(m_evmVersion); | ||||
| 	_compiler.setOptimiserSettings( | ||||
| 		m_optimiseYul ? | ||||
| 		OptimiserSettings::full() : | ||||
| 		OptimiserSettings::minimal() | ||||
| 	); | ||||
| 	compiler().setMetadataFormat(CompilerStack::MetadataFormat::NoMetadata); | ||||
| 	compiler().setMetadataHash(CompilerStack::MetadataHash::None); | ||||
| 	_compiler.setMetadataFormat(CompilerStack::MetadataFormat::NoMetadata); | ||||
| 	_compiler.setMetadataHash(CompilerStack::MetadataHash::None); | ||||
| } | ||||
| 
 | ||||
| void SyntaxTest::parseAndAnalyze() | ||||
| { | ||||
| 	setupCompiler(); | ||||
| 
 | ||||
| 	if (compiler().parse() && compiler().analyze()) | ||||
| 		try | ||||
| 	try | ||||
| 	{ | ||||
| 		runFramework(withPreamble(m_sources.sources), PipelineStage::Compilation); | ||||
| 		if (!pipelineSuccessful() && stageSuccessful(PipelineStage::Analysis)) | ||||
| 		{ | ||||
| 			if (!compiler().compile()) | ||||
| 			{ | ||||
| 				ErrorList const& errors = compiler().errors(); | ||||
| 				auto codeGeneretionErrorCount = count_if(errors.cbegin(), errors.cend(), [](auto const& error) { | ||||
| 					return error->type() == Error::Type::CodeGenerationError; | ||||
| 				}); | ||||
| 				auto errorCount = count_if(errors.cbegin(), errors.cend(), [](auto const& error) { | ||||
| 					return Error::isError(error->type()); | ||||
| 				}); | ||||
| 				// failing compilation after successful analysis is a rare case,
 | ||||
| 				// it assumes that errors contain exactly one error, and the error is of type Error::Type::CodeGenerationError
 | ||||
| 				if (codeGeneretionErrorCount != 1 || errorCount != 1) | ||||
| 					BOOST_THROW_EXCEPTION(runtime_error("Compilation failed even though analysis was successful.")); | ||||
| 			} | ||||
| 		} | ||||
| 		catch (UnimplementedFeatureError const& _e) | ||||
| 		{ | ||||
| 			m_errorList.emplace_back(SyntaxTestError{ | ||||
| 				"UnimplementedFeatureError", | ||||
| 				nullopt, | ||||
| 				errorMessage(_e), | ||||
| 				"", | ||||
| 				-1, | ||||
| 				-1 | ||||
| 			ErrorList const& errors = compiler().errors(); | ||||
| 			auto codeGeneretionErrorCount = count_if(errors.cbegin(), errors.cend(), [](auto const& error) { | ||||
| 				return error->type() == Error::Type::CodeGenerationError; | ||||
| 			}); | ||||
| 			auto errorCount = count_if(errors.cbegin(), errors.cend(), [](auto const& error) { | ||||
| 				return Error::isError(error->type()); | ||||
| 			}); | ||||
| 			// failing compilation after successful analysis is a rare case,
 | ||||
| 			// it assumes that errors contain exactly one error, and the error is of type Error::Type::CodeGenerationError
 | ||||
| 			if (codeGeneretionErrorCount != 1 || errorCount != 1) | ||||
| 				BOOST_THROW_EXCEPTION(runtime_error("Compilation failed even though analysis was successful.")); | ||||
| 		} | ||||
| 	} | ||||
| 	catch (UnimplementedFeatureError const& _e) | ||||
| 	{ | ||||
| 		m_errorList.emplace_back(SyntaxTestError{ | ||||
| 			"UnimplementedFeatureError", | ||||
| 			nullopt, | ||||
| 			errorMessage(_e), | ||||
| 			"", | ||||
| 			-1, | ||||
| 			-1 | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	filterObtainedErrors(); | ||||
| } | ||||
| @ -146,4 +144,3 @@ void SyntaxTest::filterObtainedErrors() | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -48,7 +48,7 @@ public: | ||||
| 	); | ||||
| 
 | ||||
| protected: | ||||
| 	virtual void setupCompiler(); | ||||
| 	void setupCompiler(CompilerStack& _compiler) override; | ||||
| 	void parseAndAnalyze() override; | ||||
| 	virtual void filterObtainedErrors(); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user