mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	prepare_report: Replace hard-coded optimize setting with selectable presets
This commit is contained in:
		
							parent
							
								
									f9a3c094a6
								
							
						
					
					
						commit
						10670d6286
					
				| @ -4,6 +4,10 @@ const fs = require('fs') | ||||
| 
 | ||||
| const compiler = require('solc') | ||||
| 
 | ||||
| SETTINGS_PRESETS = { | ||||
|     'legacy-optimize':    {optimize: true}, | ||||
|     'legacy-no-optimize': {optimize: false}, | ||||
| } | ||||
| 
 | ||||
| function loadSource(sourceFileName, stripSMTPragmas) | ||||
| { | ||||
| @ -23,96 +27,112 @@ function cleanString(string) | ||||
|     return (string !== '' ? string : undefined) | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| let inputFiles = [] | ||||
| let stripSMTPragmas = false | ||||
| let firstFileArgumentIndex = 2 | ||||
| let presets = [] | ||||
| 
 | ||||
| if (process.argv.length >= 3 && process.argv[2] === '--strip-smt-pragmas') | ||||
| for (let i = 2; i < process.argv.length; ++i) | ||||
| { | ||||
|     stripSMTPragmas = true | ||||
|     firstFileArgumentIndex = 3 | ||||
|     if (process.argv[i] === '--strip-smt-pragmas') | ||||
|         stripSMTPragmas = true | ||||
|     else if (process.argv[i] === '--preset') | ||||
|     { | ||||
|         if (i + 1 === process.argv.length) | ||||
|             throw Error("Option --preset was used, but no preset name given.") | ||||
| 
 | ||||
|         presets.push(process.argv[i + 1]) | ||||
|         ++i; | ||||
|     } | ||||
|     else | ||||
|         inputFiles.push(process.argv[i]) | ||||
| } | ||||
| 
 | ||||
| for (const optimize of [false, true]) | ||||
| if (presets.length === 0) | ||||
|     presets = ['legacy-no-optimize', 'legacy-optimize'] | ||||
| 
 | ||||
| for (const preset of presets) | ||||
|     if (!(preset in SETTINGS_PRESETS)) | ||||
|         throw Error(`Invalid preset name: ${preset}.`) | ||||
| 
 | ||||
| for (const preset of presets) | ||||
| { | ||||
|     for (const filename of process.argv.slice(firstFileArgumentIndex)) | ||||
|     settings = SETTINGS_PRESETS[preset] | ||||
| 
 | ||||
|     for (const filename of inputFiles) | ||||
|     { | ||||
|         if (filename !== undefined) | ||||
|         let input = { | ||||
|             language: 'Solidity', | ||||
|             sources: { | ||||
|                 [filename]: {content: loadSource(filename, stripSMTPragmas)} | ||||
|             }, | ||||
|             settings: { | ||||
|                 optimizer: {enabled: settings.optimize}, | ||||
|                 outputSelection: {'*': {'*': ['evm.bytecode.object', 'metadata']}} | ||||
|             } | ||||
|         } | ||||
|         if (!stripSMTPragmas) | ||||
|             input['settings']['modelChecker'] = {engine: 'none'} | ||||
| 
 | ||||
|         let serializedOutput | ||||
|         let result | ||||
|         const serializedInput = JSON.stringify(input) | ||||
| 
 | ||||
|         let internalCompilerError = false | ||||
|         try | ||||
|         { | ||||
|             let input = { | ||||
|                 language: 'Solidity', | ||||
|                 sources: { | ||||
|                     [filename]: {content: loadSource(filename, stripSMTPragmas)} | ||||
|                 }, | ||||
|                 settings: { | ||||
|                     optimizer: {enabled: optimize}, | ||||
|                     outputSelection: {'*': {'*': ['evm.bytecode.object', 'metadata']}} | ||||
|                 } | ||||
|             } | ||||
|             if (!stripSMTPragmas) | ||||
|                 input['settings']['modelChecker'] = {engine: 'none'} | ||||
|             serializedOutput = compiler.compile(serializedInput) | ||||
|         } | ||||
|         catch (exception) | ||||
|         { | ||||
|             internalCompilerError = true | ||||
|         } | ||||
| 
 | ||||
|             let serializedOutput | ||||
|             let result | ||||
|             const serializedInput = JSON.stringify(input) | ||||
|         if (!internalCompilerError) | ||||
|         { | ||||
|             result = JSON.parse(serializedOutput) | ||||
| 
 | ||||
|             let internalCompilerError = false | ||||
|             try | ||||
|             { | ||||
|                 serializedOutput = compiler.compile(serializedInput) | ||||
|             } | ||||
|             catch (exception) | ||||
|             { | ||||
|                 internalCompilerError = true | ||||
|             } | ||||
| 
 | ||||
|             if (!internalCompilerError) | ||||
|             { | ||||
|                 result = JSON.parse(serializedOutput) | ||||
| 
 | ||||
|                 if ('errors' in result) | ||||
|                     for (const error of result['errors']) | ||||
|                         // JSON interface still returns contract metadata in case of an internal compiler error while
 | ||||
|                         // CLI interface does not. To make reports comparable we must force this case to be detected as
 | ||||
|                         // an error in both cases.
 | ||||
|                         if (['UnimplementedFeatureError', 'CompilerError', 'CodeGenerationError'].includes(error['type'])) | ||||
|                         { | ||||
|                             internalCompilerError = true | ||||
|                             break | ||||
|                         } | ||||
|             } | ||||
| 
 | ||||
|             if ( | ||||
|                 internalCompilerError || | ||||
|                 !('contracts' in result) || | ||||
|                 Object.keys(result['contracts']).length === 0 || | ||||
|                 Object.keys(result['contracts']).every(file => Object.keys(result['contracts'][file]).length === 0) | ||||
|             ) | ||||
|                 // NOTE: do not exit here because this may be run on source which cannot be compiled
 | ||||
|                 console.log(filename + ': <ERROR>') | ||||
|             else | ||||
|                 for (const contractFile in result['contracts']) | ||||
|                     for (const contractName in result['contracts'][contractFile]) | ||||
|             if ('errors' in result) | ||||
|                 for (const error of result['errors']) | ||||
|                     // JSON interface still returns contract metadata in case of an internal compiler error while
 | ||||
|                     // CLI interface does not. To make reports comparable we must force this case to be detected as
 | ||||
|                     // an error in both cases.
 | ||||
|                     if (['UnimplementedFeatureError', 'CompilerError', 'CodeGenerationError'].includes(error['type'])) | ||||
|                     { | ||||
|                         const contractResults = result['contracts'][contractFile][contractName] | ||||
| 
 | ||||
|                         let bytecode = '<NO BYTECODE>' | ||||
|                         let metadata = '<NO METADATA>' | ||||
| 
 | ||||
|                         if ( | ||||
|                             'evm' in contractResults && | ||||
|                             'bytecode' in contractResults['evm'] && | ||||
|                             'object' in contractResults['evm']['bytecode'] && | ||||
|                             cleanString(contractResults.evm.bytecode.object) !== undefined | ||||
|                         ) | ||||
|                             bytecode = cleanString(contractResults.evm.bytecode.object) | ||||
| 
 | ||||
|                         if ('metadata' in contractResults && cleanString(contractResults.metadata) !== undefined) | ||||
|                             metadata = contractResults.metadata | ||||
| 
 | ||||
|                         console.log(filename + ':' + contractName + ' ' + bytecode) | ||||
|                         console.log(filename + ':' + contractName + ' ' + metadata) | ||||
|                         internalCompilerError = true | ||||
|                         break | ||||
|                     } | ||||
|         } | ||||
| 
 | ||||
|         if ( | ||||
|             internalCompilerError || | ||||
|             !('contracts' in result) || | ||||
|             Object.keys(result['contracts']).length === 0 || | ||||
|             Object.keys(result['contracts']).every(file => Object.keys(result['contracts'][file]).length === 0) | ||||
|         ) | ||||
|             // NOTE: do not exit here because this may be run on source which cannot be compiled
 | ||||
|             console.log(filename + ': <ERROR>') | ||||
|         else | ||||
|             for (const contractFile in result['contracts']) | ||||
|                 for (const contractName in result['contracts'][contractFile]) | ||||
|                 { | ||||
|                     const contractResults = result['contracts'][contractFile][contractName] | ||||
| 
 | ||||
|                     let bytecode = '<NO BYTECODE>' | ||||
|                     let metadata = '<NO METADATA>' | ||||
| 
 | ||||
|                     if ( | ||||
|                         'evm' in contractResults && | ||||
|                         'bytecode' in contractResults['evm'] && | ||||
|                         'object' in contractResults['evm']['bytecode'] && | ||||
|                         cleanString(contractResults.evm.bytecode.object) !== undefined | ||||
|                     ) | ||||
|                         bytecode = cleanString(contractResults.evm.bytecode.object) | ||||
| 
 | ||||
|                     if ('metadata' in contractResults && cleanString(contractResults.metadata) !== undefined) | ||||
|                         metadata = contractResults.metadata | ||||
| 
 | ||||
|                     console.log(filename + ':' + contractName + ' ' + bytecode) | ||||
|                     console.log(filename + ':' + contractName + ' ' + metadata) | ||||
|                 } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -26,12 +26,29 @@ class CompilerInterface(Enum): | ||||
|     STANDARD_JSON = 'standard-json' | ||||
| 
 | ||||
| 
 | ||||
| class SettingsPreset(Enum): | ||||
|     LEGACY_OPTIMIZE = 'legacy-optimize' | ||||
|     LEGACY_NO_OPTIMIZE = 'legacy-no-optimize' | ||||
| 
 | ||||
| 
 | ||||
| class SMTUse(Enum): | ||||
|     PRESERVE = 'preserve' | ||||
|     DISABLE = 'disable' | ||||
|     STRIP_PRAGMAS = 'strip-pragmas' | ||||
| 
 | ||||
| 
 | ||||
| @dataclass(frozen=True) | ||||
| class CompilerSettings: | ||||
|     optimize: bool | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def from_preset(preset: SettingsPreset): | ||||
|         return { | ||||
|             SettingsPreset.LEGACY_OPTIMIZE:    CompilerSettings(optimize=True), | ||||
|             SettingsPreset.LEGACY_NO_OPTIMIZE: CompilerSettings(optimize=False), | ||||
|         }[preset] | ||||
| 
 | ||||
| 
 | ||||
| @dataclass(frozen=True) | ||||
| class ContractReport: | ||||
|     contract_name: str | ||||
| @ -190,13 +207,15 @@ def parse_cli_output(source_file_name: Path, cli_output: str) -> FileReport: | ||||
| def prepare_compiler_input( | ||||
|     compiler_path: Path, | ||||
|     source_file_name: Path, | ||||
|     optimize: bool, | ||||
|     force_no_optimize_yul: bool, | ||||
|     interface: CompilerInterface, | ||||
|     preset: SettingsPreset, | ||||
|     smt_use: SMTUse, | ||||
|     metadata_option_supported: bool, | ||||
| ) -> Tuple[List[str], str]: | ||||
| 
 | ||||
|     settings = CompilerSettings.from_preset(preset) | ||||
| 
 | ||||
|     if interface == CompilerInterface.STANDARD_JSON: | ||||
|         json_input: dict = { | ||||
|             'language': 'Solidity', | ||||
| @ -204,7 +223,7 @@ def prepare_compiler_input( | ||||
|                 str(source_file_name): {'content': load_source(source_file_name, smt_use)} | ||||
|             }, | ||||
|             'settings': { | ||||
|                 'optimizer': {'enabled': optimize}, | ||||
|                 'optimizer': {'enabled': settings.optimize}, | ||||
|                 'outputSelection': {'*': {'*': ['evm.bytecode.object', 'metadata']}}, | ||||
|             } | ||||
|         } | ||||
| @ -220,7 +239,7 @@ def prepare_compiler_input( | ||||
|         compiler_options = [str(source_file_name), '--bin'] | ||||
|         if metadata_option_supported: | ||||
|             compiler_options.append('--metadata') | ||||
|         if optimize: | ||||
|         if settings.optimize: | ||||
|             compiler_options.append('--optimize') | ||||
|         elif force_no_optimize_yul: | ||||
|             compiler_options.append('--no-optimize-yul') | ||||
| @ -259,9 +278,9 @@ def detect_metadata_cli_option_support(compiler_path: Path): | ||||
| def run_compiler( | ||||
|     compiler_path: Path, | ||||
|     source_file_name: Path, | ||||
|     optimize: bool, | ||||
|     force_no_optimize_yul: bool, | ||||
|     interface: CompilerInterface, | ||||
|     preset: SettingsPreset, | ||||
|     smt_use: SMTUse, | ||||
|     metadata_option_supported: bool, | ||||
|     tmp_dir: Path, | ||||
| @ -272,9 +291,9 @@ def run_compiler( | ||||
|         (command_line, compiler_input) = prepare_compiler_input( | ||||
|             compiler_path, | ||||
|             Path(source_file_name.name), | ||||
|             optimize, | ||||
|             force_no_optimize_yul, | ||||
|             interface, | ||||
|             preset, | ||||
|             smt_use, | ||||
|             metadata_option_supported, | ||||
|         ) | ||||
| @ -295,9 +314,9 @@ def run_compiler( | ||||
|         (command_line, compiler_input) = prepare_compiler_input( | ||||
|             compiler_path.absolute(), | ||||
|             Path(source_file_name.name), | ||||
|             optimize, | ||||
|             force_no_optimize_yul, | ||||
|             interface, | ||||
|             preset, | ||||
|             smt_use, | ||||
|             metadata_option_supported, | ||||
|         ) | ||||
| @ -324,6 +343,7 @@ def generate_report( | ||||
|     source_file_names: List[str], | ||||
|     compiler_path: Path, | ||||
|     interface: CompilerInterface, | ||||
|     presets: List[SettingsPreset], | ||||
|     smt_use: SMTUse, | ||||
|     force_no_optimize_yul: bool, | ||||
|     report_file_path: Path, | ||||
| @ -335,16 +355,16 @@ def generate_report( | ||||
| 
 | ||||
|     try: | ||||
|         with open(report_file_path, mode='w', encoding='utf8', newline='\n') as report_file: | ||||
|             for optimize in [False, True]: | ||||
|             for preset in presets: | ||||
|                 with TemporaryDirectory(prefix='prepare_report-') as tmp_dir: | ||||
|                     for source_file_name in sorted(source_file_names): | ||||
|                         try: | ||||
|                             report = run_compiler( | ||||
|                                 compiler_path, | ||||
|                                 Path(source_file_name), | ||||
|                                 optimize, | ||||
|                                 force_no_optimize_yul, | ||||
|                                 interface, | ||||
|                                 preset, | ||||
|                                 smt_use, | ||||
|                                 metadata_option_supported, | ||||
|                                 Path(tmp_dir), | ||||
| @ -358,7 +378,7 @@ def generate_report( | ||||
|                         except subprocess.CalledProcessError as exception: | ||||
|                             print( | ||||
|                                 f"\n\nInterrupted by an exception while processing file " | ||||
|                                 f"'{source_file_name}' with optimize={optimize}\n\n" | ||||
|                                 f"'{source_file_name}' with preset={preset}\n\n" | ||||
|                                 f"COMPILER STDOUT:\n{exception.stdout}\n" | ||||
|                                 f"COMPILER STDERR:\n{exception.stderr}\n", | ||||
|                                 file=sys.stderr | ||||
| @ -367,7 +387,7 @@ def generate_report( | ||||
|                         except: | ||||
|                             print( | ||||
|                                 f"\n\nInterrupted by an exception while processing file " | ||||
|                                 f"'{source_file_name}' with optimize={optimize}\n", | ||||
|                                 f"'{source_file_name}' with preset={preset}\n\n", | ||||
|                                 file=sys.stderr | ||||
|                             ) | ||||
|                             raise | ||||
| @ -390,6 +410,15 @@ def commandline_parser() -> ArgumentParser: | ||||
|         choices=[c.value for c in CompilerInterface], | ||||
|         help="Compiler interface to use.", | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         '--preset', | ||||
|         dest='presets', | ||||
|         default=None, | ||||
|         nargs='+', | ||||
|         action='append', | ||||
|         choices=[p.value for p in SettingsPreset], | ||||
|         help="Predefined set of settings to pass to the compiler. More than one can be selected.", | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         '--smt-use', | ||||
|         dest='smt_use', | ||||
| @ -418,10 +447,19 @@ def commandline_parser() -> ArgumentParser: | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     options = commandline_parser().parse_args() | ||||
| 
 | ||||
|     if options.presets is None: | ||||
|         # NOTE: Can't put it in add_argument()'s default because then it would be always present. | ||||
|         # See https://github.com/python/cpython/issues/60603 | ||||
|         presets = [[SettingsPreset.LEGACY_NO_OPTIMIZE.value, SettingsPreset.LEGACY_OPTIMIZE.value]] | ||||
|     else: | ||||
|         presets = options.presets | ||||
| 
 | ||||
|     generate_report( | ||||
|         glob("*.sol"), | ||||
|         Path(options.compiler_path), | ||||
|         CompilerInterface(options.interface), | ||||
|         [SettingsPreset(p) for preset_group in presets for p in preset_group], | ||||
|         SMTUse(options.smt_use), | ||||
|         options.force_no_optimize_yul, | ||||
|         Path(options.report_file), | ||||
|  | ||||
| @ -9,7 +9,7 @@ from unittest_helpers import FIXTURE_DIR, LIBSOLIDITY_TEST_DIR, load_fixture, lo | ||||
| 
 | ||||
| # NOTE: This test file file only works with scripts/ added to PYTHONPATH so pylint can't find the imports | ||||
| # pragma pylint: disable=import-error | ||||
| from bytecodecompare.prepare_report import CompilerInterface, FileReport, ContractReport, SMTUse, Statistics | ||||
| from bytecodecompare.prepare_report import CompilerInterface, FileReport, ContractReport, SettingsPreset, SMTUse, Statistics | ||||
| from bytecodecompare.prepare_report import load_source, parse_cli_output, parse_standard_json_output, prepare_compiler_input | ||||
| # pragma pylint: enable=import-error | ||||
| 
 | ||||
| @ -224,7 +224,7 @@ class TestPrepareCompilerInput(PrepareReportTestBase): | ||||
|         (command_line, compiler_input) = prepare_compiler_input( | ||||
|             Path('solc'), | ||||
|             SMT_SMOKE_TEST_SOL_PATH, | ||||
|             optimize=True, | ||||
|             preset=SettingsPreset.LEGACY_OPTIMIZE, | ||||
|             force_no_optimize_yul=False, | ||||
|             interface=CompilerInterface.STANDARD_JSON, | ||||
|             smt_use=SMTUse.DISABLE, | ||||
| @ -238,7 +238,7 @@ class TestPrepareCompilerInput(PrepareReportTestBase): | ||||
|         (command_line, compiler_input) = prepare_compiler_input( | ||||
|             Path('solc'), | ||||
|             SMT_SMOKE_TEST_SOL_PATH, | ||||
|             optimize=True, | ||||
|             preset=SettingsPreset.LEGACY_OPTIMIZE, | ||||
|             force_no_optimize_yul=False, | ||||
|             interface=CompilerInterface.CLI, | ||||
|             smt_use=SMTUse.DISABLE, | ||||
| @ -273,7 +273,7 @@ class TestPrepareCompilerInput(PrepareReportTestBase): | ||||
|         (command_line, compiler_input) = prepare_compiler_input( | ||||
|             Path('solc'), | ||||
|             SMT_CONTRACT_WITH_MIXED_NEWLINES_SOL_PATH, | ||||
|             optimize=True, | ||||
|             preset=SettingsPreset.LEGACY_OPTIMIZE, | ||||
|             force_no_optimize_yul=False, | ||||
|             interface=CompilerInterface.STANDARD_JSON, | ||||
|             smt_use=SMTUse.DISABLE, | ||||
| @ -287,7 +287,7 @@ class TestPrepareCompilerInput(PrepareReportTestBase): | ||||
|         (_command_line, compiler_input) = prepare_compiler_input( | ||||
|             Path('solc'), | ||||
|             SMT_CONTRACT_WITH_MIXED_NEWLINES_SOL_PATH, | ||||
|             optimize=True, | ||||
|             preset=SettingsPreset.LEGACY_OPTIMIZE, | ||||
|             force_no_optimize_yul=True, | ||||
|             interface=CompilerInterface.CLI, | ||||
|             smt_use=SMTUse.DISABLE, | ||||
| @ -300,7 +300,7 @@ class TestPrepareCompilerInput(PrepareReportTestBase): | ||||
|         (command_line, compiler_input) = prepare_compiler_input( | ||||
|             Path('solc'), | ||||
|             SMT_SMOKE_TEST_SOL_PATH, | ||||
|             optimize=False, | ||||
|             preset=SettingsPreset.LEGACY_NO_OPTIMIZE, | ||||
|             force_no_optimize_yul=True, | ||||
|             interface=CompilerInterface.CLI, | ||||
|             smt_use=SMTUse.DISABLE, | ||||
| @ -317,7 +317,7 @@ class TestPrepareCompilerInput(PrepareReportTestBase): | ||||
|         (command_line, compiler_input) = prepare_compiler_input( | ||||
|             Path('solc'), | ||||
|             SMT_SMOKE_TEST_SOL_PATH, | ||||
|             optimize=True, | ||||
|             preset=SettingsPreset.LEGACY_OPTIMIZE, | ||||
|             force_no_optimize_yul=False, | ||||
|             interface=CompilerInterface.CLI, | ||||
|             smt_use=SMTUse.PRESERVE, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user