class GNATcovPlugin(Module): # Keep this style name synchronized with Code_Coverage.GNATcov. PROJECT_ATTRIBUTES = [ X( 'project_attribute', package='IDE_Coverage', name='Gnatcov_Mode_Switches', label="Switches in 'gnatcov' mode", description=("Extra build switches to pass to the builder when in" " 'gnatcov' mode."), editor_page='GNATcov', editor_section='Build', hide_in='wizard library_wizard', ).children(X('string')), X( 'project_attribute', name='Level_Run', label='Coverage Level', package='IDE_Coverage', editor_page='GNATcov', editor_section='Run', hide_in='wizard library_wizard', description='The coverage level to pass to gnatcov run.', ).children( X('choice').children('branch'), X('choice').children('insn'), X('choice', default='true').children('stmt'), X('choice').children('stmt+decision'), X('choice').children('stmt+mcdc'), ), X( 'project_attribute', name='Switches_Run', label='Extra switches', package='IDE_Coverage', editor_page='GNATcov', editor_section='Run', hide_in='wizard library_wizard', description='Extra build switches to pass to gnatcov run.', ).children(X('string')), X( 'project_attribute', name='Level_Coverage', label='Coverage Level', package='IDE_Coverage', editor_page='GNATcov', editor_section='Coverage', hide_in='wizard library_wizard', description='The coverage level to pass to gnatcov coverage.', ).children( X('choice').children('branch'), X('choice').children('insn'), X('choice', default='true').children('stmt'), X('choice').children('stmt+decision'), X('choice').children('stmt+mcdc'), ), X( 'project_attribute', name='Switches_Coverage', label='Extra switches', package='IDE_Coverage', editor_page='GNATcov', editor_section='Coverage', hide_in='wizard library_wizard', description='Extra build switches to pass to gnatcov coverage.', ).children(X('string')), ] BUILD_MODES = [ X('builder-mode', name='gnatcov').children( X('description').children('Build with GNATcoverage information'), X('subdir').children('gnatcov'), X('supported-model').children('builder'), X('supported-model').children('gnatmake'), X('supported-model').children('gprbuild'), X('supported-model', filter='--subdirs=').children('gnatcov-run'), X('supported-model', filter='--subdirs=').children('gnatcov-coverage'), X('supported-model', filter='--subdirs=').children('gprclean'), X('supported-model', filter='--subdirs=').children('GNATtest execution mode'), X('extra-args', sections='-cargs:Ada -cargs:C').children( X('arg').children("%attr(ide_coverage'gnatcov_mode_switches)"), X('arg').children('--subdirs=%subdir'), X('arg', section='-cargs:Ada').children('-g'), X('arg', section='-cargs:Ada').children('-fdump-scos'), X('arg', section='-cargs:Ada').children('-fpreserve-control-flow'), X('arg', section='-cargs:C').children('-g'), X('arg', section='-cargs:C').children('-fdump-scos'), X('arg', section='-cargs:C').children('-fpreserve-control-flow'), )) ] BUILD_TARGETS = [ X('target-model', name='gnatcov-build-main', category='').children( X('description').children('Build Main with the gnatcov switches'), X('command-line').children( X('arg').children('gprbuild') ), X('iconname').children('gps-build-all-symbolic'), X('switches', command='%(tool_name)s', columns='2', lines='2'), ), X('target', model='gnatcov-build-main', category='_GNATcov_', name='GNATcov Build Main', menu=PLUGIN_MENU + '/Build/').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('TRUE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('%builder'), X('arg').children('-P%PP'), X('arg').children('%subdirsarg'), X('arg').children('-s'), X('arg').children('%X'), X('arg').children('-cargs:Ada'), X('arg').children('-g'), X('arg').children('-fdump-scos'), X('arg').children('-fpreserve-control-flow'), X('arg').children('-cargs:C'), X('arg').children('-g'), X('arg').children('-fdump-scos'), X('arg').children('-fpreserve-control-flow') ) ), # Program execution under instrumented execution environment X('target-model', name='gnatcov-run', category='').children( X('description').children('Run under GNATcov for code coverage'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('run'), ), X('iconname').children('gps-build-all-symbolic'), X('switches', command='%(tool_name)s', columns='1', lines='1') ), X('target', model='gnatcov-run', category='_GNATcov_', name='Run under GNATcov', menu=PLUGIN_MENU + '/Run/').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('TRUE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('run'), X('arg').children('-P%PP'), X('arg').children('--recursive'), X('arg').children('%target'), X('arg').children('-c'), X('arg').children("%attr(ide_coverage'level_run,stmt)"), X('arg').children('-o'), X('arg').children('%O%T.trace'), X('arg').children('%E'), X('arg').children("%attr(ide_coverage'switches_run)"), X('arg').children('%X'), ), ), X('target', model='gnatcov-run', category='_GNATcov_', name='Run GNATcov with instrumentation').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('FALSE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('instrument'), X('arg').children('-P%PP'), X('arg').children('%subdirsarg'), X('arg').children('--level'), X('arg').children("%attr(ide_coverage'level_run,stmt)"), X('arg').children('--dump-trigger=atexit'), X('arg').children('%X'), ), ), X('target', model='gnatcov-build-main', category='_GNATcov_', name='GNATcov Build Coverage Runtime').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('FALSE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('%builder'), X('arg').children('-f'), X('arg').children( '%python' + \ '(gnatcov.GNATcovPlugin.' + \ 'get_coverage_runtime_project_arg())'), X('arg').children( '%python' + \ '(gnatcov.GNATcovPlugin.get_relocate_build_tree_arg())'), ) ), X('target', model='gnatcov-build-main', category='_GNATcov_', name='GNATcov Install Coverage Runtime').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('FALSE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('gprinstall'), X('arg').children('-f'), X('arg').children('-p'), X('arg').children( '%python' + \ '(gnatcov.GNATcovPlugin.' + \ 'get_coverage_runtime_project_arg())'), X('arg').children( '%python' + \ '(gnatcov.GNATcovPlugin.get_relocate_build_tree_arg())'), X('arg').children( '%python' + \ '(gnatcov.GNATcovPlugin.get_prefix_arg())') ) ), X('target', model='gnatcov-build-main', category='_GNATcov_', name='GNATcov Build Instrumented Main').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('FALSE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('%builder'), X('arg').children('-f'), X('arg').children('-p'), X('arg').children('-P%PP'), X('arg').children('%subdirsarg'), X('arg').children('--src-subdirs=gnatcov-instr'), X('arg').children( '%python' + \ '(gnatcov.GNATcovPlugin.' + \ 'get_installed_coverage_runtime_project_arg())') ) ), # Coverage report generation X('target-model', name='gnatcov-coverage', category='').children( X('description').children('Code coverage with GNATcov'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('coverage'), X('arg').children('-P%PP'), X('arg').children('--recursive'), X('arg').children('%target'), X('arg').children('--annotate=xcov'), ), X('iconname').children('gps-build-all-symbolic'), X('switches', command='%(tool_name)s', columns='1', lines='4'), ), X('target', model='gnatcov-coverage', category='_GNATcov_', name='Generate GNATcov Main Report', menu=PLUGIN_MENU + '/Generate Report/').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('TRUE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('coverage'), X('arg').children('-P%PP'), X('arg').children('--recursive'), X('arg').children('%target'), X('arg').children('-c'), X('arg').children("%attr(ide_coverage'level_coverage,stmt)"), X('arg').children('--annotate=xcov+'), X('arg').children('--output-dir=%O'), X('arg').children('-T'), X('arg').children('%O%T.trace'), X('arg').children("%attr(ide_coverage'switches_coverage)"), X('arg').children('%X'), ), ), X('target', model='gnatcov-coverage', category='_GNATcov_', name='Generate GNATcov Instrumented Main Report').children( X('target-type').children('executable'), X('read-only').children('TRUE'), X('in-menu').children('FALSE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('coverage'), X('arg').children('-P%PP'), X('arg').children('%subdirsarg'), X('arg').children('%target'), X('arg').children('-c'), X('arg').children("%attr(ide_coverage'level_coverage,stmt)"), X('arg').children('--annotate=xcov+'), X('arg').children('--output-dir=%O'), X('arg').children('-T'), X('arg').children('%O%T.srctrace'), X('arg').children("%attr(ide_coverage'switches_coverage)"), X('arg').children('%X'), ), ), ] GNATCOV_DOCUMENTATION = [ X('doc_path').children( os.path.join(gnatcov_install_dir, 'share', 'doc', 'gnatcoverage', 'html') if gnatcov_install_dir else None), X('documentation_file').children( X('name').children('gnatcov.html'), X('descr').children("GNATcoverage User's Guide"), X('category').children('GNATcoverage'), X('menu', before='About').children( "/Help/GNATcoverage/GNATcoverage User's Guide"), ), ] GNATEMU_DOCUMENTATION = [ X('doc_path').children('share/doc/gnatemu/html'), X('documentation_file').children( X('name').children('gnatemulator.html'), X('descr').children('GNATemulator Documentation'), X('category').children('GNATcoverage'), X('menu', before='About').children( '/Help/GNATcoverage/GNATemulator Documentation'), ), ] __run_gnatcov_wf_build_target = None # The 'Run GNATcoverage' workflow Build Target __run_gnatcov_instr_wf_build_target = None # The 'Run GNATcoverage with instrumentation' workflow Build Target def setup(self): # This plugin makes sense only if GNATcoverage is available. if not self.is_gnatcov_available(): return # Create all custom things that do not require GPS' GUI to be ready # (i.e.: all but menus and hooks). for xml_nodes in ( self.PROJECT_ATTRIBUTES, self.BUILD_MODES, self.GNATCOV_DOCUMENTATION, self.GNATEMU_DOCUMENTATION, ): GPS.parse_xml(list_to_xml(xml_nodes)) # Update the GNATcoverage workflow Build Targets, creating them and # showing/hiding them appropriately self.update_worflow_build_targets() # Now the parent menu is present, fill it with custom targets. GPS.parse_xml(list_to_xml(self.BUILD_TARGETS)) GPS.Hook('compilation_finished').add(self.on_compilation_finished) def project_view_changed(self): if self.is_gnatcov_available(): self.update_worflow_build_targets() def update_worflow_build_targets(self): gnatcov_available = self.is_gnatcov_available() instrumentation_supported = self.is_instrumentation_supported() if gnatcov_available and not self.__run_gnatcov_wf_build_target: workflows.create_target_from_workflow( target_name="Run GNATcoverage", workflow_name="run-gnatcov", workflow=self.run_gnatcov_wf, icon_name="gps-run-gnatcov-symbolic", parent_menu="/Build/Workflow/GNATcov/") self.__run_gnatcov_wf_build_target = \ GPS.BuildTarget("Run GNATcoverage") if instrumentation_supported: workflows.create_target_from_workflow( target_name="Run GNATcoverage with instrumentation", workflow_name="run-gnatcov-with-instrumentation", in_toolbar=False, workflow=self.run_gnatcov_with_instrumentation_wf, parent_menu=PLUGIN_MENU + "/Intrumentation/") self.__run_gnatcov_instr_wf_build_target = \ GPS.BuildTarget("Run GNATcoverage with instrumentation") if not gnatcov_available: if self.__run_gnatcov_wf_build_target: self.__run_gnatcov_wf_build_target.hide() if self.__run_gnatcov_instr_wf_build_target: self.__run_gnatcov_instr_wf_build_target.hide() elif not instrumentation_supported: if self.__run_gnatcov_instr_wf_build_target: self.__run_gnatcov_instr_wf_build_target.hide() def is_gnatcov_available(self): return os_utils.locate_exec_on_path('gnatcov') != "" def is_instrumentation_supported(self): # Check if GNATcov and GPRbuild are recent enough (after the 21 # release) for exe in 'gnatcov', 'gprbuild': try: version_out = GPS.Process( exe + " --version").get_result().splitlines()[0] matches = TOOL_VERSION_REGEXP.findall(version_out) version_major, version_minor = matches[0] except Exception: # Can happen with the GS testuite if we use a fake gnatcov exe return False if not (int(version_major) >= 22 or (int(version_major) == 21 and version_minor[-1] != "w")): GPS.Logger("GNATCOVERAGE").log("instrumentation mode not " + "supported due to an older %s" % exe) return False return True def run_gnatcov_wf(self, main_name): # Build the project with GNATcov switches p = promises.TargetWrapper("GNATcov Build Main") r = yield p.wait_on_execute() if r is not 0: GPS.Console("Messages").write("Can't build the project with " + "the GNATcov switches", mode="error") return # Get the executable to analyze exe = str(GPS.File(main_name).executable_path) # Run GNATcov on it p = promises.TargetWrapper("Run under GNATcov") r = yield p.wait_on_execute(exe) if r is not 0: GPS.Console("Messages").write("GNATcov run failed ", mode="error") return # Generate and display the GNATcov Coverage Report p = promises.TargetWrapper("Generate GNATcov Main Report") r = yield p.wait_on_execute(exe) def run_gnatcov_with_instrumentation_wf(self, main_name): # Get the executable to analyze exe = str(GPS.File(main_name).executable_path) # Build the coverage runtime p = promises.TargetWrapper("GNATcov Build Coverage Runtime") r = yield p.wait_on_execute(quiet=True) if r is not 0: GPS.Console("Messages").write("GNATcov runtime build failed ", mode="error") return # Install the coverage runtime p = promises.TargetWrapper("GNATcov Install Coverage Runtime") r = yield p.wait_on_execute(quiet=True) if r is not 0: GPS.Console("Messages").write("GNATcov runtime build failed ", mode="error") return # Run GNATcov with instrumentation on it p = promises.TargetWrapper("Run GNATcov with instrumentation") r = yield p.wait_on_execute(exe, quiet=True) if r is not 0: GPS.Console("Messages").write("GNATcov run failed ", mode="error") return # Build the instrumented main p = promises.TargetWrapper("GNATcov Build Instrumented Main") r = yield p.wait_on_execute(quiet=True) if r is not 0: GPS.Console("Messages").write("Can't build the project with " + "the GNATcov switches", mode="error") return # Go to the object directory before executing the instrumented main: we # want to produce the trace file in the object dir and not in the # project's root directory obj_dir = GPS.Project.root().object_dirs()[0] GPS.cd(obj_dir) # Build the instrumented main p = promises.ProcessWrapper(cmdargs=[exe]) r = yield p.wait_until_terminate() # Generate and display the GNATcov Coverage Report p = promises.TargetWrapper("Generate GNATcov Instrumented Main Report") r = yield p.wait_on_execute(exe, quiet=True) def reload_gnatcov_data(self): """Clean the coverage report and reload it from the files.""" # If needed, switch to GNATcov build mode. pref_name = ("Coverage-Toolchain" if "ENABLE_GCOV" in os.environ else "Coverage-Toolchain-Internal") if GPS.Preference(pref_name).get() != 'Gnatcov': GPS.Preference(pref_name).set('Gnatcov') GPS.execute_action("coverage clear from memory") if GPS.Project.root().is_harness_project(): a = GPS.CodeAnalysis.get("Coverage Report") original = GPS.Project.root().original_project().file() a.add_gcov_project_info(original) else: GPS.execute_action("coverage load data for all projects") def on_compilation_finished(self, hook, category, target_name="", mode_name="", status="", *args): """Called whenever a compilation ends.""" # If compilation failed, do nothing. if status: return if target_name in [ "Generate GNATcov Main Report", "Generate GNATcov Instrumented Main Report" ]: self.reload_gnatcov_data() @staticmethod def get_coverage_runtime_project_arg(): """ Return the path of the coverage runtime bundled with the gnatcov installation. This runtime is needed to use gnatcov with instrumentation. """ gnatcov_path = os_utils.locate_exec_on_path('gnatcov') gnatcov_dir = os.path.dirname(gnatcov_path) runtime_dir = os.path.join(gnatcov_dir, os.pardir, "share", "gnatcoverage", "gnatcov_rts") return "-P" + os.path.join(runtime_dir, "gnatcov_rts_full.gpr") @staticmethod def get_relocate_build_tree_arg(): return "--relocate-build-tree=" + tempfile.gettempdir() @staticmethod def get_prefix_arg(): return "--prefix=" + tempfile.gettempdir() @staticmethod def get_installed_coverage_runtime_project_arg(): return "--implicit-with=" + \ os.path.join(tempfile.gettempdir(), "share", "gpr", "gnatcov_rts_full.gpr")
class GNATcovPlugin(object): PLUGIN_MENU = '/Analyze/Coverage/GNATcoverage' # Keep this style name synchronized with Code_Coverage.GNATcov. PROJECT_ATTRIBUTES = [ X( 'project_attribute', package='IDE_Coverage', name='Gnatcov_Mode_Switches', label="Switches in 'gnatcov' mode", description=("Extra build switches to pass to the builder when in" " 'gnatcov' mode."), editor_page='GNATcov', editor_section='Build', hide_in='wizard library_wizard', ).children(X('string')), X( 'project_attribute', name='Level_Run', label='Coverage Level', package='IDE_Coverage', editor_page='GNATcov', editor_section='Run', hide_in='wizard library_wizard', description='The coverage level to pass to gnatcov run.', ).children( X('choice').children('branch'), X('choice').children('insn'), X('choice', default='true').children('stmt'), X('choice').children('stmt+decision'), X('choice').children('stmt+mcdc'), ), X( 'project_attribute', name='Switches_Run', label='Extra switches', package='IDE_Coverage', editor_page='GNATcov', editor_section='Run', hide_in='wizard library_wizard', description='Extra build switches to pass to gnatcov run.', ).children(X('string')), X( 'project_attribute', name='Level_Coverage', label='Coverage Level', package='IDE_Coverage', editor_page='GNATcov', editor_section='Coverage', hide_in='wizard library_wizard', description='The coverage level to pass to gnatcov coverage.', ).children( X('choice').children('branch'), X('choice').children('insn'), X('choice', default='true').children('stmt'), X('choice').children('stmt+decision'), X('choice').children('stmt+mcdc'), ), X( 'project_attribute', name='Switches_Coverage', label='Extra switches', package='IDE_Coverage', editor_page='GNATcov', editor_section='Coverage', hide_in='wizard library_wizard', description='Extra build switches to pass to gnatcov coverage.', ).children(X('string')), ] BUILD_MODES = [ X('builder-mode', name='gnatcov').children( X('description').children('Build with GNATcoverage information'), X('subdir').children('gnatcov'), X('supported-model').children('builder'), X('supported-model').children('gnatmake'), X('supported-model').children('gprbuild'), X('supported-model', filter='--subdirs=').children('gnatcov-run'), X('supported-model', filter='--subdirs=').children('gnatcov-coverage'), X('supported-model', filter='--subdirs=').children('gprclean'), X('supported-model', filter='--subdirs=').children('GNATtest execution mode'), X('extra-args', sections='-cargs:Ada -cargs:C').children( X('arg').children("%attr(ide_coverage'gnatcov_mode_switches)"), X('arg').children('--subdirs=%subdir'), X('arg', section='-cargs:Ada').children('-g'), X('arg', section='-cargs:Ada').children('-fdump-scos'), X('arg', section='-cargs:Ada').children('-fpreserve-control-flow'), X('arg', section='-cargs:C').children('-g'), X('arg', section='-cargs:C').children('-fdump-scos'), X('arg', section='-cargs:C').children('-fpreserve-control-flow'), )) ] BUILD_TARGETS = [ X('target-model', name='gnatcov-build-main', category='').children( X('description').children('Build Main with the gnatcov switches'), X('command-line').children(X('arg').children('gprbuild')), X('iconname').children('gps-build-all-symbolic'), X('switches', command='%(tool_name)s', columns='2', lines='2'), ), X('target', model='gnatcov-build-main', category='_GNATcov_', name='GNATcov Build Main', menu=PLUGIN_MENU + '/Build/').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('TRUE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('%builder'), X('arg').children('-P%PP'), X('arg').children('%subdirsarg'), X('arg').children('-s'), X('arg').children('-cargs:Ada'), X('arg').children('-g'), X('arg').children('-fdump-scos'), X('arg').children('-fpreserve-control-flow'), X('arg').children('-cargs:C'), X('arg').children('-g'), X('arg').children('-fdump-scos'), X('arg').children('-fpreserve-control-flow'))), # Program execution under instrumented execution environment X('target-model', name='gnatcov-run', category='').children( X('description').children('Run under GNATcov for code coverage'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('run'), ), X('iconname').children('gps-build-all-symbolic'), X('switches', command='%(tool_name)s', columns='1', lines='1')), X('target', model='gnatcov-run', category='_GNATcov_', name='Run under GNATcov', menu=PLUGIN_MENU + '/Run/').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('TRUE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('run'), X('arg').children('-P%PP'), X('arg').children('--recursive'), X('arg').children('%target'), X('arg').children('-c'), X('arg').children("%attr(ide_coverage'level_run,stmt)"), X('arg').children('-o'), X('arg').children('%TT.trace'), X('arg').children('%E'), X('arg').children("%attr(ide_coverage'switches_run)"), ), ), # Coverage report generation X('target-model', name='gnatcov-coverage', category='').children( X('description').children('Code coverage with GNATcov'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('coverage'), X('arg').children('-P%PP'), X('arg').children('--recursive'), X('arg').children('%target'), X('arg').children('--annotate=xcov'), ), X('iconname').children('gps-build-all-symbolic'), X('switches', command='%(tool_name)s', columns='1', lines='4'), ), X('target', model='gnatcov-coverage', category='_GNATcov_', name='Generate GNATcov Main Report', menu=PLUGIN_MENU + '/Generate Report/').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('TRUE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('coverage'), X('arg').children('-P%PP'), X('arg').children('--recursive'), X('arg').children('%target'), X('arg').children('-c'), X('arg').children("%attr(ide_coverage'level_coverage,stmt)"), X('arg').children('--annotate=xcov+'), X('arg').children('--output-dir=%O'), X('arg').children('-T'), X('arg').children('%TT.trace'), X('arg').children("%attr(ide_coverage'switches_coverage)"), ), ), ] GNATCOV_DOCUMENTATION = [ X('doc_path').children( os.path.join(gnatcov_install_dir, 'share', 'doc', 'gnatcoverage', 'html') if gnatcov_install_dir else None), X('documentation_file').children( X('name').children('gnatcov.html'), X('descr').children("GNATcoverage User's Guide"), X('category').children('GNATcoverage'), X('menu', before='About').children( "/Help/GNATcoverage/GNATcoverage User's Guide"), ), ] GNATEMU_DOCUMENTATION = [ X('doc_path').children('share/doc/gnatemu/html'), X('documentation_file').children( X('name').children('gnatemulator.html'), X('descr').children('GNATemulator Documentation'), X('category').children('GNATcoverage'), X('menu', before='About').children( '/Help/GNATcoverage/GNATemulator Documentation'), ), ] def __init__(self): # Create all custom things that do not require GPS' GUI to be ready # (i.e.: all but menus and hooks). for xml_nodes in ( self.PROJECT_ATTRIBUTES, self.BUILD_MODES, self.GNATCOV_DOCUMENTATION, self.GNATEMU_DOCUMENTATION, ): GPS.parse_xml(list_to_xml(xml_nodes)) # Create the GNATcoverage toolbar button self.create_toolbar_button() # Defer further initialization to when GPS is completely ready. GPS.Hook('gps_started').add(self.on_gps_started) def create_toolbar_button(self): workflows.create_target_from_workflow( target_name="Run GNATcoverage", workflow_name="run-gnatcov", workflow=self.run_gnatcov_wf, icon_name="gps-run-gnatcov-symbolic", parent_menu="/Build/Workflow/GNATcov/") def run_gnatcov_wf(self, main_name): # Build the project with GNATcov switches p = promises.TargetWrapper("GNATcov Build Main") r = yield p.wait_on_execute() if r is not 0: GPS.Console("Messages").write("Can't build the project with " + "the GNATcov switches", mode="error") return # Get the executable to analyze exe = str(GPS.File(main_name).executable_path) # Run GNATcov on it p = promises.TargetWrapper("Run under GNATcov") r = yield p.wait_on_execute(exe) if r is not 0: GPS.Console("Messages").write("GNATcov run failed ", mode="error") return # Generate and display the GNATcov Coverage Report p = promises.TargetWrapper("Generate GNATcov Main Report") r = yield p.wait_on_execute(exe) def on_gps_started(self, hook): # Now the parent menu is present, fill it with custom targets. GPS.parse_xml(list_to_xml(self.BUILD_TARGETS)) GPS.Hook('compilation_finished').add(self.on_compilation_finished) def reload_gnatcov_data(self): """Clean the coverage report and reload it from the files.""" # If needed, switch to GNATcov build mode. pref_name = ("Coverage-Toolchain" if "ENABLE_GCOV" in os.environ else "Coverage-Toolchain-Internal") if GPS.Preference(pref_name).get() != 'Gnatcov': GPS.Preference(pref_name).set('Gnatcov') GPS.execute_action("coverage clear from memory") if GPS.Project.root().is_harness_project(): a = GPS.CodeAnalysis.get("Coverage Report") original = GPS.Project.root().original_project().file() a.add_gcov_project_info(original) else: GPS.execute_action("coverage load data for all projects") def on_compilation_finished(self, hook, category, target_name="", mode_name="", status="", *args): """Called whenever a compilation ends.""" # If compilation failed, do nothing. if status: return if target_name in ["Generate GNATcov Main Report"]: self.reload_gnatcov_data()
class GNATcovPlugin(object): PLUGIN_MENU = '/Tools/GNATcov/' # Keep this style name synchronized with Code_Coverage.GNATcov. PROJECT_ATTRIBUTES = [ X( 'project_attribute', package='IDE_Coverage', name='Gnatcov_Mode_Switches', label="Switches in 'gnatcov' mode", description=("Extra build switches to pass to the builder when in" " 'gnatcov' mode."), editor_page='GNATcov', editor_section='Build', hide_in='wizard library_wizard', ).children(X('string')), X( 'project_attribute', name='Level_Run', label='Coverage Level', package='IDE_Coverage', editor_page='GNATcov', editor_section='Run', hide_in='wizard library_wizard', description='The coverage level to pass to gnatcov run.', ).children( X('choice').children('branch'), X('choice').children('insn'), X('choice', default='true').children('stmt'), X('choice').children('stmt+decision'), X('choice').children('stmt+mcdc'), ), X( 'project_attribute', name='Switches_Run', label='Extra switches', package='IDE_Coverage', editor_page='GNATcov', editor_section='Run', hide_in='wizard library_wizard', description='Extra build switches to pass to gnatcov run.', ).children(X('string')), X( 'project_attribute', name='Level_Coverage', label='Coverage Level', package='IDE_Coverage', editor_page='GNATcov', editor_section='Coverage', hide_in='wizard library_wizard', description='The coverage level to pass to gnatcov coverage.', ).children( X('choice').children('branch'), X('choice').children('insn'), X('choice', default='true').children('stmt'), X('choice').children('stmt+decision'), X('choice').children('stmt+mcdc'), ), X( 'project_attribute', name='Switches_Coverage', label='Extra switches', package='IDE_Coverage', editor_page='GNATcov', editor_section='Coverage', hide_in='wizard library_wizard', description='Extra build switches to pass to gnatcov coverage.', ).children(X('string')), ] BUILD_MODES = [ X('builder-mode', name='gnatcov').children( X('description').children('Build with GNATcoverage information'), X('subdir').children('gnatcov'), X('supported-model').children('builder'), X('supported-model').children('gnatmake'), X('supported-model').children('gprbuild'), X('supported-model', filter='--subdirs=').children('gnatcov-run'), X('supported-model', filter='--subdirs=').children('gnatcov-coverage'), X('supported-model', filter='--subdirs=').children('gprclean'), X('extra-args', sections='-cargs').children( X('arg').children("%attr(ide_coverage'gnatcov_mode_switches)"), X('arg').children('--subdirs=%subdir'), X('arg', section='-cargs').children('-g'), X('arg', section='-cargs').children('-fdump-scos'), X('arg', section='-cargs').children('-fpreserve-control-flow'), )) ] BUILD_TARGETS = [ # Program execution under instrumented execution environment X('target-model', name='gnatcov-run', category='').children( X('description').children('Run under GNATcov for code coverage'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('run'), ), X('iconname').children('gps-build-all-symbolic'), X('switches', command='%(tool_name)s', columns='2', lines='2'), ), X('target', model='gnatcov-run', category='GNATcov run', name='Run under GNATcov', menu=PLUGIN_MENU).children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('TRUE'), X('read-only').children('TRUE'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('run'), X('arg').children('-P%PP'), X('arg').children('--recursive'), X('arg').children('%target'), X('arg').children('-c'), X('arg').children("%attr(ide_coverage'level_run,stmt)"), X('arg').children('-o'), X('arg').children('%TT.trace'), X('arg').children('%E'), X('arg').children("%attr(ide_coverage'switches_run)"), ), ), # Coverage report generation X('target-model', name='gnatcov-coverage', category='').children( X('description').children('Code coverage with GNATcov'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('coverage'), X('arg').children('-P%PP'), X('arg').children('--recursive'), X('arg').children('%target'), X('arg').children('--annotate=xcov'), ), X('iconname').children('gps-build-all-symbolic'), X('switches', command='%(tool_name)s', columns='1', lines='4'), ), X('target', model='gnatcov-coverage', category='GNATcov coverage', name='Generate GNATcov Main Report', menu=PLUGIN_MENU).children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('TRUE'), X('read-only').children('TRUE'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('coverage'), X('arg').children('-P%PP'), X('arg').children('--recursive'), X('arg').children('%target'), X('arg').children('-c'), X('arg').children("%attr(ide_coverage'level_coverage,stmt)"), X('arg').children('--annotate=xcov+'), X('arg').children('--output-dir=%O'), X('arg').children('-T'), X('arg').children('%TT.trace'), X('arg').children("%attr(ide_coverage'switches_coverage)"), ), ), ] GNATCOV_DOCUMENTATION = [ X('doc_path').children( os.path.join(gnatcov_install_dir, 'share', 'doc', 'gnatcoverage', 'html') if gnatcov_install_dir else None), X('documentation_file').children( X('name').children('gnatcov.html'), X('descr').children("GNATcoverage User's Guide"), X('category').children('GNATcoverage'), X('menu', before='About').children( "/Help/GNATcoverage/GNATcoverage User's Guide"), ), ] GNATEMU_DOCUMENTATION = [ X('doc_path').children('share/doc/gnatemu/html'), X('documentation_file').children( X('name').children('gnatemulator.html'), X('descr').children('GNATemulator Documentation'), X('category').children('GNATcoverage'), X('menu', before='About').children( '/Help/GNATcoverage/GNATemulator Documentation'), ), ] def __init__(self): # Create all custom things that do not require GPS' GUI to be ready # (i.e.: all but menus and hooks). for xml_nodes in ( self.PROJECT_ATTRIBUTES, self.BUILD_MODES, self.GNATCOV_DOCUMENTATION, self.GNATEMU_DOCUMENTATION, ): GPS.parse_xml(list_to_xml(xml_nodes)) # Defer further initialization to when GPS is completely ready. GPS.Hook('gps_started').add(self.on_gps_started) def on_gps_started(self, hook): # Now the parent menu is present, fill it with custom targets. GPS.parse_xml(list_to_xml(self.BUILD_TARGETS)) GPS.Hook('compilation_finished').add(self.on_compilation_finished) def reload_gnatcov_data(self): """Clean the coverage report and reload it from the files.""" # If needed, switch to GNATcov build mode. if GPS.Preference("Coverage-Toolchain").get() != 'Gnatcov': GPS.Preference("Coverage-Toolchain").set('Gnatcov') GPS.execute_action("/Tools/Coverage/Clear coverage from memory") if GPS.Project.root().is_harness_project(): a = GPS.CodeAnalysis.get("Coverage Report") original = GPS.Project.root().original_project().file() a.add_gcov_project_info(original) else: GPS.execute_action("/Tools/Coverage/Load data for all projects") def on_compilation_finished(self, hook, category, target_name="", mode_name="", status=""): """Called whenever a compilation ends.""" # If compilation failed, do nothing. if status: return if target_name in ["Generate GNATcov Main Report"]: self.reload_gnatcov_data()
class GNATcovPlugin(Module): # Keep this style name synchronized with Code_Coverage.GNATcov. BUILD_MODES = [ X('builder-mode', name='gnatcov').children( X('description').children('Build with GNATcoverage information'), X('subdir').children('gnatcov'), X('supported-model').children('builder'), X('supported-model').children('gnatmake'), X('supported-model').children('gprbuild'), X('supported-model', filter='--subdirs=').children('gnatcov-run'), X('supported-model', filter='--subdirs=').children('gnatcov-coverage'), X('supported-model', filter='--subdirs=').children('gprclean'), X('supported-model', filter='--subdirs=').children('GNATtest execution mode'), X('extra-args', sections='-cargs:Ada -cargs:C').children( X('arg').children("%attr(ide_coverage'gnatcov_mode_switches)"), X('arg').children('--subdirs=%subdir'), X('arg', section='-cargs:Ada').children('-g'), X('arg', section='-cargs:Ada').children('-fdump-scos'), X('arg', section='-cargs:Ada').children('-fpreserve-control-flow'), X('arg', section='-cargs:C').children('-g'), X('arg', section='-cargs:C').children('-fdump-scos'), X('arg', section='-cargs:C').children('-fpreserve-control-flow'), )) ] BUILD_TARGET_MODELS = [ X('target-model', name='gnatcov-build-main', category='').children( X('description').children('Build Main with the gnatcov switches'), X('command-help').children('{help}'), X('command-line').children(X('arg').children('gprbuild')), X('iconname').children('gps-build-all-symbolic'), X('switches', command='%(tool_name)s', columns='2', lines='2'), ), # Program execution under instrumented execution environment X('target-model', name='gnatcov-run', category='').children( X('description').children('Run under GNATcov for code coverage'), X('command-help').children('{help}'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('run'), ), X('iconname').children('gps-build-all-symbolic'), X("switches", command="%(tool_name)s", columns="1", lines="1").children( X("combo", line="1", column="1", noswitch="stmt", separator="=", switch="--level", label="Coverage Level", tip="""The coverage level to pass to gnatcov run."""). children( X('combo-entry', label='branch', value='branch'), X('combo-entry', label='insn', value='insn'), X('combo-entry', label='stmt', value='stmt'), X('combo-entry', label='stmt+decision', value='stmt+decision'), X('combo-entry', label='stmt+mcdc', value='stmt+mcdc'), ), ), ), # Coverage report generation X('target-model', name='gnatcov-coverage', category='').children( X('description').children('Code coverage with GNATcov'), X('command-help').children('{help}'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('coverage'), X('arg').children('%X'), X('arg').children('-P%PP'), X('arg').children('--recursive'), X('arg').children('%target'), X('arg').children('--annotate=xcov'), ), X('iconname').children('gps-build-all-symbolic'), X("switches", command="%(tool_name)s", columns="1", lines="1").children( X("combo", line="1", column="1", noswitch="stmt", separator="=", switch="--level", label="Coverage Level", tip="""The coverage level to pass to gnatcov coverage."""). children( X('combo-entry', label='branch', value='branch'), X('combo-entry', label='insn', value='insn'), X('combo-entry', label='stmt', value='stmt'), X('combo-entry', label='stmt+decision', value='stmt+decision'), X('combo-entry', label='stmt+mcdc', value='stmt+mcdc'), ), ), ), ] BINARY_TRACES_BUILD_TARGETS = [ X('target', model='gnatcov-build-main', category='_GNATcov_', name='GNATcov Build Main', menu=BINARY_TRACES_MENU + '/Build/').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('TRUE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('%builder'), X('arg').children('-P%PP'), X('arg').children('%subdirsarg'), X('arg').children('-s'), X('arg').children('%X'), X('arg').children('-cargs:Ada'), X('arg').children('-g'), X('arg').children('-fdump-scos'), X('arg').children('-fpreserve-control-flow'), X('arg').children('-cargs:C'), X('arg').children('-g'), X('arg').children('-fdump-scos'), X('arg').children('-fpreserve-control-flow'))), X('target', model='gnatcov-run', category='_GNATcov_', name='Run under GNATcov', menu=BINARY_TRACES_MENU + '/Run/').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('TRUE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('run'), X('arg').children('-P%PP'), X('arg').children('--recursive'), X('arg').children('%target'), X('arg').children('-c'), X('arg').children("%attr(ide_coverage'level_coverage,stmt)"), X('arg').children('-o'), X('arg').children('%O%T.trace'), X('arg').children('%E'), X('arg').children("%attr(ide_coverage'switches_run)"), X('arg').children('%X'), ), ), X('target', model='gnatcov-coverage', category='_GNATcov_', name='Generate GNATcov Main Report', menu=BINARY_TRACES_MENU + '/Generate Report/').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('TRUE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('coverage'), X('arg').children('-P%PP'), X('arg').children('--recursive'), X('arg').children('%target'), X('arg').children('-c'), X('arg').children("%attr(ide_coverage'level_coverage,stmt)"), X('arg').children('--annotate=xcov+'), X('arg').children('--output-dir=%O'), X('arg').children('-T'), X('arg').children('%O%T.trace'), X('arg').children("%attr(ide_coverage'switches_coverage)"), X('arg').children('%X'), ), ), ] SOURCE_TRACES_BUILD_TARGETS = [ X('target', model='gnatcov-run', category='_GNATcov_', name='Run GNATcov with instrumentation', menu=SOURCE_TRACES_MENU + '/Instrumentation/').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('TRUE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('instrument'), X('arg').children('-P%PP'), X('arg').children('%subdirsarg'), X('arg').children('--level'), X('arg').children("%attr(ide_coverage'level_run,stmt)"), X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' + 'get_dump_trigger_arg())'), X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' + 'get_dump_channel_arg())'), X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' + 'get_dump_filename_simple_arg())'), X('arg').children('%X'), ), ), X('target', model='gnatcov-build-main', category='_GNATcov_', name='GNATcov Build Coverage Runtime', menu=SOURCE_TRACES_MENU + '/Build/').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('FALSE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('%builder'), X('arg').children('%X'), X('arg').children('-f'), X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' + 'get_rts_arg())'), X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' + 'get_coverage_runtime_project_arg())'), X('arg').children( '%python' + '(gnatcov.GNATcovPlugin.get_relocate_build_tree_arg())'), )), X('target', model='gnatcov-build-main', category='_GNATcov_', name='GNATcov Install Coverage Runtime').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('FALSE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('gprinstall'), X('arg').children('%X'), X('arg').children('%target'), X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' + 'get_rts_arg())'), X('arg').children('-f'), X('arg').children('-p'), X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' + 'get_coverage_runtime_project_arg())'), X('arg').children( '%python' + '(gnatcov.GNATcovPlugin.get_relocate_build_tree_arg())'), X('arg').children( '%python' + '(gnatcov.GNATcovPlugin.get_prefix_arg())'))), X('target', model='gnatcov-build-main', category='_GNATcov_', name='GNATcov Build Instrumented Main', menu=SOURCE_TRACES_MENU + '/Build/').children( X('target-type').children('executable'), X('in-toolbar').children('FALSE'), X('in-menu').children('TRUE'), X('read-only').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('%builder'), X('arg').children('%X'), X('arg').children('-p'), X('arg').children('%python' + '(gnatcov.GNATcovPlugin.' + 'get_rts_arg())'), X('arg').children('-P%PP'), X('arg').children('%subdirsarg'), X('arg').children('--src-subdirs=gnatcov-instr'), X('arg').children( '%python' + '(gnatcov.GNATcovPlugin.' + 'get_installed_coverage_runtime_project_arg())'))), X('target', model='gnatcov-coverage', category='_GNATcov_', name='Generate GNATcov Instrumented Main Report', menu=SOURCE_TRACES_MENU + '/Generate Report/').children( X('target-type').children('executable'), X('read-only').children('TRUE'), X('in-menu').children('TRUE'), X('output-parsers').children( 'output_chopper utf_converter console_writer end_of_build'), X('iconname').children('gps-build-all-symbolic'), X('launch-mode').children('MANUALLY'), X('command-line').children( X('arg').children('gnatcov'), X('arg').children('coverage'), X('arg').children('-P%PP'), X('arg').children('%subdirsarg'), X('arg').children('%target'), X('arg').children('-c'), X('arg').children("%attr(ide_coverage'level_coverage,stmt)"), X('arg').children('--annotate=xcov+'), X('arg').children('--output-dir=%O'), X('arg').children('-T'), X('arg').children('%O%T.srctrace'), X('arg').children("%attr(ide_coverage'switches_coverage)"), X('arg').children('%X'), ), ), ] GNATCOV_DOCUMENTATION = [ X('doc_path').children(gnatcov_doc_path), X('documentation_file').children( X('name').children(gnatcov_doc_index), X('descr').children("GNATcoverage User's Guide"), X('category').children('GNATcoverage'), X('menu', before='About').children( "/Help/GNATcoverage/GNATcoverage User's Guide"), ), ] GNATEMU_DOCUMENTATION = [ X('doc_path').children('share/doc/gnatemu/html'), X('documentation_file').children( X('name').children('gnatemulator.html'), X('descr').children('GNATemulator Documentation'), X('category').children('GNATcoverage'), X('menu', before='About').children( '/Help/GNATcoverage/GNATemulator Documentation'), ), ] __run_gnatcov_wf_build_target = None # The 'Run GNATcoverage' workflow Build Target __run_gnatcov_instr_wf_build_target = None # The 'Run GNATcoverage with instrumentation' workflow Build Target def setup(self): # This plugin makes sense only if GNATcoverage is available. if not self.is_gnatcov_available(): return # Create all custom things that do not require GPS' GUI to be ready # (i.e.: all but menus and hooks). for xml_nodes in ( self.BUILD_MODES, self.GNATCOV_DOCUMENTATION, self.GNATEMU_DOCUMENTATION, ): GPS.parse_xml(list_to_xml(xml_nodes)) # Update the GNATcoverage workflow Build Targets, creating them and # showing/hiding them appropriately. Also fill the custom targets. process = GPS.Process(["gnatcov", "--help"]) help_msg = process.get_result() GPS.parse_xml( list_to_xml(self.BUILD_TARGET_MODELS).format(help=help_msg)) self.update_worflow_build_targets() # Try to retrieve a prebuilt GNATcov runtime from the history global prebuilt_runtime_path prebuilt_runtime_path = GPS.History.get(RUNTIME_PATH_HIST_KEY, most_recent=True) GPS.Hook('compilation_finished').add(self.on_compilation_finished) def teardown(self): GNATcovPlugin.remove_secure_temp_dir() def project_view_changed(self): if self.is_gnatcov_available(): self.update_worflow_build_targets() def update_worflow_build_targets(self): gnatcov_available = self.is_gnatcov_available() instrumentation_supported = self.is_instrumentation_supported() if gnatcov_available and not self.__run_gnatcov_wf_build_target: if self.is_binary_supported(): workflows.create_target_from_workflow( target_name="Run GNATcoverage", workflow_name="run-gnatcov", workflow=self.run_gnatcov_wf, in_toolbar=not instrumentation_supported, icon_name="gps-run-gnatcov-symbolic", parent_menu=BINARY_TRACES_MENU + "/Run All Actions/") self.__run_gnatcov_wf_build_target = \ GPS.BuildTarget("Run GNATcoverage") GPS.parse_xml(list_to_xml(self.BINARY_TRACES_BUILD_TARGETS)) if instrumentation_supported: workflows.create_target_from_workflow( target_name="Run GNATcoverage with instrumentation", workflow_name="run-gnatcov-with-instrumentation", in_toolbar=True, icon_name="gps-run-gnatcov-symbolic", workflow=self.run_gnatcov_with_instrumentation_wf, parent_menu=SOURCE_TRACES_MENU + "/Run All Actions/") self.__run_gnatcov_instr_wf_build_target = \ GPS.BuildTarget("Run GNATcoverage with instrumentation") # We want the toolbar to be ordered according to the coverage # process: # GNATcoverage source traces # -> Instrument # -> Build # -> Run # -> Generate Report # # As the Run action is a a workflow, and is not included in # SOURCE_TRACES_BUILD_TARGETS, we have to incorporate it in the # middle. We thus first parse the XML corresponding to the # Instrument and Build steps, then parse the XML for the Run # workflow, and in the end, for the Generate Report build # target. # Instrument and Build GPS.parse_xml(list_to_xml( self.SOURCE_TRACES_BUILD_TARGETS[:4])) workflows.create_target_from_workflow( target_name="Run instrumented main", workflow_name="run-instrumented-main", in_toolbar=False, icon_name="gps-run-gnatcov-symbolic", workflow=self.run_instrumented_main_wf, parent_menu=SOURCE_TRACES_MENU + "/Run/") # Generate Report GPS.parse_xml(list_to_xml( self.SOURCE_TRACES_BUILD_TARGETS[4:])) if not gnatcov_available: if self.__run_gnatcov_wf_build_target: self.__run_gnatcov_wf_build_target.hide() if self.__run_gnatcov_instr_wf_build_target: self.__run_gnatcov_instr_wf_build_target.hide() elif not instrumentation_supported: if self.__run_gnatcov_instr_wf_build_target: self.__run_gnatcov_instr_wf_build_target.hide() def is_gnatcov_available(self): return os_utils.locate_exec_on_path('gnatcov') != "" # Return the tool version as (major version, minor version) def version(self, exe): latest_version = (23, 0) version_out = GPS.Process(exe + " --version").get_result() # Support a gnatcov built in dev mode if version_out == "GNATcoverage development-tree": return latest_version matches = TOOL_VERSION_REGEXP.findall(version_out.splitlines()[0]) return matches[0] def is_binary_supported(self): # Starting from GNATcov version 22.0, binary traces are no longer # supported in native. try: version_major, _ = self.version("gnatcov") except Exception: # Can happen with the GS testuite if we use a fake gnatcov exe return True return GPS.get_target() != "" or int(version_major) < 22 def is_instrumentation_supported(self): # Check if GNATcov and GPRbuild are recent enough (after the 21 # release) for exe in 'gnatcov', 'gprbuild': try: version_major, version_minor = self.version(exe) except Exception as e: # Can happen with the GS testuite if we use a fake gnatcov exe GPS.Console().write(str(e)) return False if not (int(version_major) >= 22 or (int(version_major) == 21 and version_minor[-1] != "w")): GPS.Logger("GNATCOVERAGE").log("instrumentation mode not " + "supported due to an older %s" % exe) return False return True def run_gnatcov_wf(self, main_name): # Build the project with GNATcov switches p = promises.TargetWrapper("GNATcov Build Main") r = yield p.wait_on_execute() if r != 0: GPS.Console("Messages").write("Can't build the project with " + "the GNATcov switches", mode="error") return # Get the executable to analyze exe = str(GPS.File(main_name).executable_path) # Run GNATcov on it p = promises.TargetWrapper("Run under GNATcov") r = yield p.wait_on_execute(exe) if r != 0: GPS.Console("Messages").write("GNATcov run failed ", mode="error") return # Generate and display the GNATcov Coverage Report p = promises.TargetWrapper("Generate GNATcov Main Report") r = yield p.wait_on_execute(exe) @workflows.run_as_workflow def run_instrumented_main_wf(self, main_name, generate=False): exe = str(GPS.File(main_name).executable_path) # Go to the object directory before executing the instrumented main: we # want to produce the trace file in the object dir and not in the # project's root directory obj_dir = GPS.Project.root().object_dirs()[0] GPS.cd(obj_dir) # Clean the previous trace file if it exists (the run can fails and # then the trace file will not be overwritten: it will show outdated # data) srctrace_filename = os.path.join(obj_dir, exe + ".srctrace") try: os.remove(srctrace_filename) except FileNotFoundError: pass # Run the instrumented main (through GNATemulator for cross targets) # it will generate the new trace file. target = GPS.get_target() if target == "": cmdargs = [exe] p = promises.ProcessWrapper(cmdargs) GPS.Console().write(' '.join(cmdargs)) status, output = yield p.wait_until_terminate(show_if_error=True) if status != 0: GPS.Console("Messages").write( "Failed to execute main with status " + str(status)) else: # Launch the instrumented executable through GNATemulator cmdargs = GPS.BuildTarget( "Run GNATemulator").get_expanded_command_line() cmdargs.append(exe) GPS.Console().write(' '.join(cmdargs) + "\n") gnatemu_promise = promises.ProcessWrapper(cmdargs=cmdargs) status, output = yield gnatemu_promise.wait_until_terminate( show_if_error=True) # Put the output in a file and use 'gnatcov extract-base64-trace' # to retrieve the traces information from it out_filename = os.path.join(obj_dir, exe + ".out") with open(out_filename, "w") as f: f.write(output) extract_trace_cmd = [ "gnatcov", "extract-base64-trace", out_filename, srctrace_filename ] GPS.Console().write(' '.join(extract_trace_cmd) + "\n") status = GPS.Process(extract_trace_cmd).wait() if status != 0: GPS.Console().write( "Could not extract traces info from executable's output", mode="error") if status == 0 and generate: # Generate and display the GNATcov Coverage Report p = promises.TargetWrapper( "Generate GNATcov Instrumented Main Report") yield p.wait_on_execute(exe, quiet=True) return status def run_gnatcov_with_instrumentation_wf(self, main_name): # Get the executable to analyze exe = str(GPS.File(main_name).executable_path) # Don't build/install the GNATcov runtime if a prebuilt one has been # specified. if not prebuilt_runtime_path: # Build the coverage runtime p = promises.TargetWrapper("GNATcov Build Coverage Runtime") r = yield p.wait_on_execute(quiet=True) if r != 0: GPS.Console("Messages").write("GNATcov runtime build failed ", mode="error") return # Install the coverage runtime p = promises.TargetWrapper("GNATcov Install Coverage Runtime") r = yield p.wait_on_execute(quiet=True) if r != 0: GPS.Console("Messages").write("GNATcov runtime build failed ", mode="error") return else: GPS.Console().write("\nPrebuilt runtime is used: %s\n" % prebuilt_runtime_path) # Run GNATcov with instrumentation on it p = promises.TargetWrapper("Run GNATcov with instrumentation") r = yield p.wait_on_execute(exe, quiet=True) if r != 0: GPS.Console("Messages").write("GNATcov instrumentation failed ", mode="error") return # Build the instrumented main p = promises.TargetWrapper("GNATcov Build Instrumented Main") r = yield p.wait_on_execute(quiet=True) if r != 0: GPS.Console("Messages").write("Can't build the project with " + "the GNATcov switches", mode="error") return self.run_instrumented_main_wf(main_name, True) def reload_gnatcov_data(self): """Clean the coverage report and reload it from the files.""" # If needed, switch to GNATcov build mode. pref_name = ("Coverage-Toolchain" if "ENABLE_GCOV" in os.environ else "Coverage-Toolchain-Internal") if GPS.Preference(pref_name).get() != 'Gnatcov': GPS.Preference(pref_name).set('Gnatcov') GPS.execute_action("coverage clear from memory") if GPS.Project.root().is_harness_project(): a = GPS.CodeAnalysis.get("Coverage Report") original = GPS.Project.root().original_project().file() a.add_gcov_project_info(original) else: GPS.execute_action("coverage load data for all projects") def on_compilation_finished(self, hook, category, target_name="", mode_name="", status="", *args): """Called whenever a compilation ends.""" # If compilation failed, do nothing. if status: return if target_name in [ "Generate GNATcov Main Report", "Generate GNATcov Instrumented Main Report" ]: self.reload_gnatcov_data() @staticmethod @interactive(category="GNATcov", menu=(SOURCE_TRACES_MENU + "/Select prebuilt runtime"), description=("Select the .gpr project of an " + "installed GNATcoverage prebuilt runtime."), name="select gnatcov runtime") def set_prebuilt_runtime_action(): """ Create an action and a menu to be able to select an installed prebuilt GNATcoverage runtime. The path to this runtime is stored in the history, making it persistant accross sessions. """ global prebuilt_runtime_path hist_path = GPS.History.get(RUNTIME_PATH_HIST_KEY, most_recent=True) base_dir = "" if hist_path: base_dir = os.path.dirname(hist_path) file = GPS.MDI.file_selector(base_dir=base_dir) prebuilt_runtime_path = file.path GPS.History.add(RUNTIME_PATH_HIST_KEY, file.path) @staticmethod def get_secure_temp_dir(): """ Create a secure temp directory using tempfile.mkdtemp. This directory should be deleted manually after usage (see the GNATcovPlugin.remove_secure_temp_dir function for that). """ global secure_temp_dir if not secure_temp_dir: secure_temp_dir = tempfile.mkdtemp() return secure_temp_dir @staticmethod def remove_secure_temp_dir(): """ Remove the secure temp dir created via GNATcovPlugin.get_secure_temp_dir, if it exists. """ global secure_temp_dir if secure_temp_dir: try: shutil.rmtree(secure_temp_dir) except Exception as e: GPS.Logger("GNATCOVERAGE").log( "exception when removing temp dir: %s" % e) secure_temp_dir = None @staticmethod def get_coverage_runtime_gpr_name(): """ Return the absolute path to the gnatcov instrumentation runtime project to use for the current target/runtime. """ runtime_attr = GPS.get_runtime() # Locate the directory that contains the gnatcov instrumentation # runtime projects. gnatcov_path = os_utils.locate_exec_on_path("gnatcov") gnatcov_dir = os.path.dirname(gnatcov_path) rts_dir = os.path.join(gnatcov_dir, os.pardir, "share", "gnatcoverage", "gnatcov_rts") default = os.path.join(rts_dir, "gnatcov_rts.gpr") full = os.path.join(rts_dir, "gnatcov_rts_full.gpr") # Pick the restricted profile for BB runtimes ("default"), the "full" # one otherwise (unless the full one does not exist, after the # gnatcov_rts merge): if ("ravenscar" in runtime_attr or "zfp" in runtime_attr or "light" in runtime_attr or "embedded" in runtime_attr or not os.path.exists(full)): return default else: return full @staticmethod def get_rts_arg(): """ Return the needed --RTS option for gnatcov command lines. """ runtime_attr = GPS.get_runtime() if runtime_attr == "": return "" else: return "--RTS=%s" % runtime_attr @staticmethod def get_coverage_runtime_project_arg(): """ Return the project option ("-P...") to use the gnatcov instrumentation runtime for the current target/runtime. """ return "-P" + GNATcovPlugin.get_coverage_runtime_gpr_name() @staticmethod def get_dump_trigger_arg(): runtime_attr = GPS.get_runtime() # If we have a BB runtime profile around, pick the closest # plausible match. Assume atexit is usable otherwise: if ("ravenscar" in runtime_attr or "light-tasking" in runtime_attr or "embedded" in runtime_attr): return "--dump-trigger=ravenscar-task-termination" elif ("zfp" in runtime_attr or "light" in runtime_attr): return "--dump-trigger=main-end" else: return "--dump-trigger=atexit" @staticmethod def get_dump_channel_arg(): target_attr = GPS.get_target() if target_attr == "": return "--dump-channel=bin-file" else: return "--dump-channel=base64-stdout" @staticmethod def get_dump_filename_simple_arg(): target_attr = GPS.get_target() if target_attr == "": return "--dump-filename-simple" else: return "" @staticmethod def get_relocate_build_tree_arg(): return "--relocate-build-tree=" + GNATcovPlugin.get_secure_temp_dir() @staticmethod def get_prefix_arg(): return "--prefix=" + GNATcovPlugin.get_secure_temp_dir() @staticmethod def get_installed_coverage_runtime_project_arg(): if prebuilt_runtime_path: return "--implicit-with=%s" % prebuilt_runtime_path else: install_dir = os.path.join(GNATcovPlugin.get_secure_temp_dir(), "share", "gpr") gpr_name = os.path.basename( GNATcovPlugin.get_coverage_runtime_gpr_name()) return "--implicit-with=" + os.path.join(install_dir, gpr_name)
secure_temp_dir = None # The secure temp directory used to build the GNATcov runtime in # instrumentation mode. prebuilt_runtime_path = None # Used to store the prebuilt GNATcov runtime path, if the user specified # one. PROJECT_ATTRIBUTES = [ X( 'project_attribute', package='IDE_Coverage', name='Gnatcov_Mode_Switches', label="Switches in 'gnatcov' mode", description=("Extra build switches to pass to the builder when in" " 'gnatcov' mode."), editor_page='GNATcov', editor_section='Build', hide_in='wizard library_wizard', ).children(X('string')), X( 'project_attribute', name='Level_Run', label='Coverage Level', package='IDE_Coverage', editor_page='GNATcov', editor_section='Run', hide_in='wizard library_wizard', description='The coverage level to pass to gnatcov run.', ).children(
class GNATfuzzPlugin(Module): """The main support plugin for GNATfuzz""" # Define some build targets BUILD_TARGETS = [ X("target-model", name="gnatfuzz-generate-model", category="").children( X("description").children("Launch gnatfuzz generate"), X("command-line").children(), X("iconname").children("gps-build-all-symbolic"), X("switches", command="gnatfuzz", columns="2", lines="2").children( X( "field", line="1", column="1", label="Output directory", switch="-o", tip="the directory in which to generate the harness", )), ), X("target-model", name="gnatfuzz-fuzz-model", category="").children( X("description").children("Launch gnatfuzz fuzz"), X("command-line").children(), X("iconname").children("gps-build-all-symbolic"), X("switches", command="gnatfuzz", columns="2", lines="1").children( X( "spin", line="1", column="1", label="Fuzzing cores", separator="=", switch="--cores", default="0", min="0", max="32", tip="The number of cores to use. Use 0 for automatic.", ), X( "combo", line="1", column="1", label="AFL mode", separator="=", switch="--afl-mode", default="PERSIST", tip="The AFL run mode. See the AFL++ documentation.", ).children( X("combo-entry", label="PLAIN", value="afl_plain"), X("combo-entry", label="PERSIST", value="afl_persist"), # "DEFER" and "DEFER_AND_PERSIST" are not available yet; # uncomment these lines when they are available. # X("combo-entry", label="DEFER", value="afl_defer"), # X( # "combo-entry", # label="DEFER_AND_PERSIST", # value="afl_defer_and_persist", # ), ), ), ), X( "target", model="custom", category="_GNATfuzz_", name="gnatfuzz analyze project", ).children( X("target-type").children(""), X("in-toolbar").children("FALSE"), X("in-menu").children("FALSE"), X("read-only").children("TRUE"), X("output-parsers").children( "output_chopper utf_converter console_writer end_of_build"), X("iconname").children("gps-build-all-symbolic"), X("launch-mode").children("MANUALLY"), X("command-line").children( X("arg").children("gnatfuzz"), X("arg").children("analyze"), X("arg").children("-P%PP"), X("arg").children("%subdirsarg"), X("arg").children("%X"), ), ), X( "target", model="custom", category="_GNATfuzz_", name="gnatfuzz analyze file", ).children( X("target-type").children(""), X("in-toolbar").children("FALSE"), X("in-menu").children("FALSE"), X("read-only").children("TRUE"), X("output-parsers").children( "output_chopper utf_converter console_writer end_of_build"), X("iconname").children("gps-build-all-symbolic"), X("launch-mode").children("MANUALLY"), X("command-line").children( X("arg").children("gnatfuzz"), X("arg").children("analyze"), X("arg").children("-P%PP"), X("arg").children("%subdirsarg"), X("arg").children("%X"), X("arg").children("-S"), X("arg").children("%F"), ), ), X( "target", model="gnatfuzz-fuzz-model", category="_GNATfuzz_", name="gnatfuzz fuzz", menu="", ).children( X("target-type").children(""), X("in-toolbar").children("FALSE"), X("in-menu").children("FALSE"), X("read-only").children("TRUE"), X("output-parsers").children( "output_chopper utf_converter console_writer end_of_build"), X("iconname").children("gps-build-all-symbolic"), X("launch-mode").children("MANUALLY_WITH_DIALOG"), X("command-line").children( X("arg").children("gnatfuzz"), X("arg").children("fuzz"), X("arg").children("-P%PP"), X("arg").children("%subdirsarg"), ), ), X( "target", model="gnatfuzz-generate-model", category="_GNATfuzz_", name="gnatfuzz generate", menu="", ).children( X("target-type").children(""), X("in-toolbar").children("FALSE"), X("in-menu").children("FALSE"), X("read-only").children("TRUE"), X("output-parsers").children( "output_chopper utf_converter console_writer end_of_build"), X("iconname").children("gps-build-all-symbolic"), X("launch-mode").children("MANUALLY_WITH_DIALOG"), X("command-line").children( X("arg").children("gnatfuzz"), X("arg").children("generate"), X("arg").children("-P%PP"), X("arg").children("%subdirsarg"), X("arg").children("%X"), ), ), ] def setup(self): # This plugin makes sense only if GNATcoverage is available: # return immediately if not. if not os_utils.locate_exec_on_path("gnatfuzz"): return # These fields are set when the project is a harness project self.user_project = None # The original user project self.output_dir = None # The gnatfuzz output dir # Create the build targets GPS.parse_xml(list_to_xml(self.BUILD_TARGETS)) ref_menu = "Analyze" # Create the actions make_interactive( self.gnatfuzz_analyze_project, category="GNATfuzz", name="gnatfuzz analyze project workflow", menu="/GNATfuzz/Analyze project", before=ref_menu, ) make_interactive( self.gnatfuzz_analyze_file, category="GNATfuzz", name="gnatfuzz analyze file workflow", menu="/GNATfuzz/Analyze file", before=ref_menu, ) make_interactive( self.gnatfuzz_generate, category="GNATfuzz", name="gnatfuzz generate workflow", ) make_interactive( self.gnatfuzz_fuzz_start_stop, filter=self.is_harness_project, category="GNATfuzz", name="gnatfuzz fuzz workflow", menu="/GNATfuzz/Start\\/Stop Fuzzing Session", before=ref_menu, ) make_interactive( self.switch_to_user_project, filter=self.is_harness_project, category="GNATfuzz", name="gnatfuzz switch to user project", menu="/GNATfuzz/Switch to User Project", before=ref_menu, ) # Call the project changed hook to refresh the harness flags self.project_view_changed() def switch_to_user_project(self): """Switch back from the harness project to the user project""" if self.user_project is not None: GPS.Project.load(self.user_project) def teardown(self): """Inherited""" # Nothing to do pass def is_harness_project(self, context): return self.user_project is not None def project_view_changed(self): """React to a project view change""" project_dir = os.path.dirname(GPS.Project.root().file().name()) config_file = os.path.join(project_dir, "fuzz_config.json") if os.path.exists(config_file): # A config file has been found: this is a harness project # Read the contents of the config field with open(config_file, "r") as f: decoded = json.load(f) self.user_project = decoded["user_project"] self.output_dir = decoded["output_directory"] else: # This is not a harness project self.user_project = None self.output_dir = None def error(self, msg): """Convenience function to log an error in the Messages""" GPS.Console("Messages").write(msg + "\n", mode="error") ########### # Analyze # ########### def on_fuzzable_subprogram_click(self, message): """React to a click on a "fuzzable subprogram" message box""" # This is akin to a compilation: save everything that needs saving GPS.MDI.save_all() output_dir = os.path.join(GPS.Project.root().object_dirs()[0], "fuzz_harness") if os.path.exists(output_dir): shutil.rmtree(output_dir) analyze_report_file = self.path_to_analyze_json() if not os.path.exists(analyze_report_file): self.error(f"Analyze file not found: {analyze_report_file}") return # Launch "gnatfuzz generate" GPS.BuildTarget("gnatfuzz generate").execute( extra_args=[ "-o", output_dir, "--analysis", analyze_report_file, "--subprogram-id", str(message.analyze_id), ], synchronous=True, ) # TODO: make this a workflow, in case this takes a long time. harness_project = os.path.join(output_dir, "fuzz_testing", "fuzz_test.gpr") if os.path.exists(harness_project): r = GPS.MDI.yes_no_dialog( "Harness generation successful.\n\nSwitch to harness project?") if r: GPS.Project.load(harness_project) def create_messages_from_analyze_json_entry(self, entry): """Create a GS Messages from one toplevel entry in "fuzzable_subprograms" in analyze.json """ file = entry["source_filename"] line = entry["start_line"] if "id" in entry: # If there is an "id", this is a non-generic case m = GPS.Message( category="Fuzzable Subprograms", file=GPS.File(file), line=line, column=1, text="Fuzzable subprogram", show_on_editor_side=True, show_in_locations=True, auto_jump_to_first=False, ) m.set_subprogram( self.on_fuzzable_subprogram_click, "gps-compile-symbolic", "Generate fuzz harness", ) m.__setattr__("analyze_id", entry["id"]) elif "instantiations" in entry: # This is a generic case: create a message with the chain if len(entry["instantiations"]) == 0: self.error("analyze.json: 'instantiations' empty") for sub in entry["instantiations"]: id = sub["id"] # First go through the chain to craft a label for the # toplevel message labels = [] chain = sub["instantiation_chain"] if len(chain) == 0: self.error("analyze.json: 'instantiation_chain' empty") chain.reverse() # for chain_entry in chain: labels.append(chain_entry["label"]) text = "Fuzzable subprogram: " + ":".join(labels) m = GPS.Message( category="Fuzzable Subprograms", file=GPS.File(file), line=line, column=1, text=text, show_on_editor_side=True, show_in_locations=True, auto_jump_to_first=False, ) m.set_subprogram( self.on_fuzzable_subprogram_click, "gps-compile-symbolic", "Generate fuzz harness: " + ":".join(labels), ) m.__setattr__("analyze_id", id) for chain_entry in chain: label = chain_entry["label"] chain_line = chain_entry["start_line"] chain_filename = chain_entry["source_filename"] base = os.path.basename(chain_filename) sub_msg = f"... instantiated in {label} at {base}:{chain_line}" # Create a nested message for each entry in the chain m.create_nested_message(GPS.File(chain_filename), chain_line, 1, sub_msg) else: self.error("analyze.json entry without 'id' nor 'instantiations'") def path_to_analyze_json(self): """Return the path to analyze.json""" return os.path.join(GPS.Project.root().object_dirs()[0], "gnatfuzz", "analyze.json") def process_analyze_messages(self): """Process the analyze.json file""" # Find the analyze report analyze_report_file = self.path_to_analyze_json() if not os.path.exists(analyze_report_file): self.error(f"Analyze file not found: {analyze_report_file}") return # Open the analyze report with open(analyze_report_file, "r") as f: try: decoded = json.load(f) except Exception: self.error(f"{analyze_report_file} corrupted") if "fuzzable_subprograms" not in decoded: self.error(f"{analyze_report_file} missing 'fuzzable_subprograms'") return # Create messages for entry in decoded["fuzzable_subprograms"]: self.create_messages_from_analyze_json_entry(entry) def gnatfuzz_analyze_project_workflow(self, task): """Workflow for 'gnatfuzz analyze project'.""" # Launch the analyze target in the background p = promises.TargetWrapper("gnatfuzz analyze project") r = yield p.wait_on_execute() if r != 0: self.error("gnatfuzz analyze returned nonzero") return self.process_analyze_messages() def gnatfuzz_analyze_file_workflow(self, task): """Workflow for 'gnatfuzz analyze'.""" # Launch the analyze target in the background p = promises.TargetWrapper("gnatfuzz analyze file") r = yield p.wait_on_execute() if r != 0: self.error("gnatfuzz analyze returned nonzero") return self.process_analyze_messages() def clear_analyze_messages(self): """Clear messages generated by 'gnatfuzz analyze'""" for m in GPS.Message.list(category="Fuzzable Subprograms"): m.remove() def gnatfuzz_analyze_project(self): """Action to launch the 'gnatfuzz analyze' workflow""" self.clear_analyze_messages() workflows.task_workflow("gnatfuzz analyze", self.gnatfuzz_analyze_project_workflow) def gnatfuzz_analyze_file(self): self.clear_analyze_messages() """Action to launch the 'gnatfuzz analyze' workflow on a file""" workflows.task_workflow("gnatfuzz analyze", self.gnatfuzz_analyze_file_workflow) ############ # Generate # ############ def gnatfuzz_generate_workflow(self, task): """The 'gnatfuzz generate' workflow""" p = promises.TargetWrapper("gnatfuzz generate") r = yield p.wait_on_execute() if r != 0: GPS.Console("Messages").write("gnatfuzz generate returned nonzero", mode="error") return def gnatfuzz_generate(self): """Action to launch the 'gnatfuzz generate' workflow""" workflows.task_workflow("gnatfuzz generate", self.gnatfuzz_generate_workflow) ######## # Fuzz # ######## def gnatfuzz_fuzz_workflow(self, task): """The 'gnatfuzz fuzz' workflow""" # Move away the previous fuzzing session dir fuzz_session_dir = os.path.join(self.output_dir, "fuzz_testing", "session") if os.path.exists(fuzz_session_dir): shutil.rmtree(fuzz_session_dir) # Generate the -X switches args = [] for variable, value in GPS.Project.scenario_variables().items(): # We pass all -X switches except the ones that are internal # to gnatfuzz. if not (variable.startswith("GNATFUZZ") or variable == "AFL_MODE"): args.append(f"-X{variable}={value}") args.extend([ f"--corpus-path={self.output_dir}/fuzz_testing/starting_corpus", f"--stop-criteria={self.output_dir}" "/fuzz_testing/user_configuration/stop_criteria.xml", ]) GPS.BuildTarget("gnatfuzz fuzz").execute( extra_args=args, synchronous=False, ) # Create a CodeAnalysis object to store the coverage data a = GPS.CodeAnalysis.get("gnatfuzz") xcov_files = {} # Keys: full path, values: timestamp # Launch the GNATfuzz view GPS.execute_action("open GNATfuzz view") # Clear the GNATfuzz view GPS.execute_action("clear GNATfuzz view") # Monitor the disk for the presence of xcov files while True: # This is interrupted by the user calling the menu again, # in which case the Task will be removed: see at the bottom # of the loop. yield promises.timeout(FUZZ_MONITOR_TIMEOUT) if not os.path.exists(fuzz_session_dir): self.error( f"fuzz session directory {fuzz_session_dir} not found") self.stop_fuzz() break # Monitor for coverage files found_xcov_files = glob.glob( os.path.join(fuzz_session_dir, "coverage_output", "*.xcov")) for xcov in found_xcov_files: (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(xcov) timestamp = time.ctime(mtime) if xcov not in xcov_files or xcov_files[xcov] != timestamp: xcov_files[xcov] = timestamp base = os.path.basename(xcov)[:-5] a.add_gcov_file_info(GPS.File(base), GPS.File(xcov), raise_window=False) a.show_file_coverage_info(GPS.File(base)) # Monitor for crashes view = get_gnatfuzz_view() if view is not None: view.refresh() # The end condition tasks = [t for t in GPS.Task.list() if t.name() == "gnatfuzz fuzz"] if len(tasks) == 0: break return def is_fuzz_running(self): """Return True if "gnatfuzz fuzz" is running""" tasks = [t for t in GPS.Task.list() if t.name() == "gnatfuzz fuzz"] return len(tasks) > 0 def stop_fuzz(self): tasks = [t for t in GPS.Task.list() if t.name() == "gnatfuzz fuzz"] if len(tasks) > 0: tasks[0].interrupt() def gnatfuzz_fuzz_start_stop(self): """Action to start/stop the 'gnatfuzz fuzz' workflow""" # Check whether we have a "gnatfuzz fuzz" process running: if so, # interrupt it, and the workflow should terminate. if self.is_fuzz_running(): self.stop_fuzz() else: workflows.task_workflow(FUZZ_TASK_NAME, self.gnatfuzz_fuzz_workflow)