From d3aed2c4b336c7cb9d23c13b2b36144c42228a79 Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Tue, 1 Jul 2025 08:18:36 -0400 Subject: [PATCH] GP-5545_5600: ProgramLoader and more flexible loader args --- ...ImportAllProgramsFromADirectoryScript.java | 27 +- .../java/ghidra/app/script/GhidraScript.java | 33 +- .../app/util/headless/AnalyzeHeadless.java | 11 +- .../app/util/headless/HeadlessAnalyzer.java | 124 ++-- .../app/util/headless/HeadlessOptions.java | 31 +- .../app/util/importer/AutoImporter.java | 478 +++++++------- .../util/importer/CsHintLoadSpecChooser.java | 9 +- .../util/importer/LcsHintLoadSpecChooser.java | 51 +- .../app/util/importer/LoadSpecChooser.java | 26 +- .../importer/LoaderArgsOptionChooser.java | 32 +- .../app/util/importer/OptionChooser.java | 34 +- .../app/util/importer/ProgramLoader.java | 582 ++++++++++++++++++ .../app/util/importer/SingleLoaderFilter.java | 44 +- .../opinion/AbstractLibrarySupportLoader.java | 177 ++++-- .../opinion/AbstractOrdinalSupportLoader.java | 9 +- .../util/opinion/AbstractProgramLoader.java | 38 +- .../opinion/AbstractProgramWrapperLoader.java | 10 +- .../ghidra/app/util/opinion/BinaryLoader.java | 4 +- .../ghidra/app/util/opinion/ElfLoader.java | 6 +- .../ghidra/app/util/opinion/GdtLoader.java | 6 +- .../ghidra/app/util/opinion/GzfLoader.java | 6 +- .../app/util/opinion/IntelHexLoader.java | 8 +- .../ghidra/app/util/opinion/LoadResults.java | 140 +++-- .../java/ghidra/app/util/opinion/Loaded.java | 225 ++++--- .../ghidra/app/util/opinion/LoadedOpen.java | 56 ++ .../java/ghidra/app/util/opinion/Loader.java | 15 +- .../ghidra/app/util/opinion/MachoLoader.java | 26 +- .../app/util/opinion/MotorolaHexLoader.java | 8 +- .../ghidra/app/util/opinion/MzLoader.java | 6 +- .../ghidra/app/util/opinion/XmlLoader.java | 8 +- .../ghidra/base/project/GhidraProject.java | 236 +++++-- .../plugin/importer/ImporterPlugin.java | 2 +- .../plugin/importer/ImporterUtilities.java | 102 +-- .../importer/LoadLibrariesOptionsDialog.java | 14 +- .../importer/tasks/ImportBatchTask.java | 34 +- .../program/examiner/ProgramExaminer.java | 30 +- .../program/util/ExternalSymbolResolver.java | 50 +- .../ghidra/app/util/opinion/ApkLoader.java | 10 +- .../android/multidex/MultiDexLinker.java | 42 +- .../ghidra_scripts/ImportMSLibs.java | 51 +- .../MSLibBatchImportWorker.java | 60 +- .../RecursiveRecursiveMSLibImport.java | 38 +- .../src/main/java/sarif/SarifLoader.java | 4 +- .../TutorialDebuggerMaintenance.java | 29 +- .../TutorialDebuggerScreenShots.java | 21 +- .../TraceRmiLauncherServicePluginTest.java | 18 +- .../ReferencesPluginScreenShots.java | 31 +- 47 files changed, 2026 insertions(+), 976 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/ProgramLoader.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadedOpen.java diff --git a/Ghidra/Features/Base/ghidra_scripts/ImportAllProgramsFromADirectoryScript.java b/Ghidra/Features/Base/ghidra_scripts/ImportAllProgramsFromADirectoryScript.java index 0d6e8d89a4..aa2452f7ef 100644 --- a/Ghidra/Features/Base/ghidra_scripts/ImportAllProgramsFromADirectoryScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/ImportAllProgramsFromADirectoryScript.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,8 +20,8 @@ import java.io.File; import java.io.IOException; import ghidra.app.script.GhidraScript; -import ghidra.app.util.importer.AutoImporter; import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.importer.ProgramLoader; import ghidra.app.util.opinion.LoadResults; import ghidra.framework.model.DomainFolder; import ghidra.program.model.lang.LanguageCompilerSpecPair; @@ -53,21 +53,20 @@ public class ImportAllProgramsFromADirectoryScript extends GhidraScript { continue; } - LoadResults loadResults = null; - try { - loadResults = AutoImporter.importByLookingForLcs(file, state.getProject(), - folder.getPathname(), language.getLanguage(), language.getCompilerSpec(), this, - log, monitor); - loadResults.getPrimary().save(state.getProject(), log, monitor); + try (LoadResults loadResults = ProgramLoader.builder() + .source(file) + .project(state.getProject()) + .projectFolderPath(folder.getPathname()) + .language(language.getLanguage()) + .compiler(language.getCompilerSpec()) + .log(log) + .monitor(monitor) + .load()) { + loadResults.getPrimary().save(monitor); } catch (IOException e) { println("Unable to import program from file " + file.getName()); } - finally { - if (loadResults != null) { - loadResults.release(this); - } - } println(log.toString()); log.clear(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java index 95ae00aa3e..5ac301762b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java @@ -43,8 +43,7 @@ import ghidra.app.tablechooser.TableChooserExecutor; import ghidra.app.util.demangler.DemangledObject; import ghidra.app.util.demangler.DemanglerUtil; import ghidra.app.util.dialog.AskAddrDialog; -import ghidra.app.util.importer.AutoImporter; -import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.importer.ProgramLoader; import ghidra.app.util.opinion.*; import ghidra.app.util.query.TableService; import ghidra.app.util.viewer.field.BrowserCodeUnitFormat; @@ -3620,8 +3619,8 @@ public abstract class GhidraScript extends FlatProgramAPI { /** * Attempts to import the specified file. It attempts to detect the format and * automatically import the file. If the format is unable to be determined, then - * null is returned. For more control over the import process, {@link AutoImporter} may be - * directly called. + * null is returned. For more control over the import process, {@link ProgramLoader} may be + * directly used. *

* NOTE: The returned {@link Program} is not automatically saved into the current project. *

@@ -3634,11 +3633,12 @@ public abstract class GhidraScript extends FlatProgramAPI { * @throws Exception if any exceptions occur while importing */ public Program importFile(File file) throws Exception { - try { - LoadResults loadResults = AutoImporter.importByUsingBestGuess(file, - state.getProject(), null, this, new MessageLog(), monitor); - loadResults.releaseNonPrimary(this); - return loadResults.getPrimaryDomainObject(); + try (LoadResults loadResults = ProgramLoader.builder() + .source(file) + .project(state.getProject()) + .monitor(monitor) + .load()) { + return loadResults.getPrimaryDomainObject(this); } catch (LoadException e) { return null; @@ -3647,7 +3647,7 @@ public abstract class GhidraScript extends FlatProgramAPI { /** * Imports the specified file as raw binary. For more control over the import process, - * {@link AutoImporter} may be directly called. + * {@link ProgramLoader} may be directly used. *

* NOTE: It is the responsibility of the script that calls this method to release the returned * {@link Program} with {@link DomainObject#release(Object consumer)} when it is no longer @@ -3661,10 +3661,15 @@ public abstract class GhidraScript extends FlatProgramAPI { */ public Program importFileAsBinary(File file, Language language, CompilerSpec compilerSpec) throws Exception { - try { - Loaded loaded = AutoImporter.importAsBinary(file, state.getProject(), null, - language, compilerSpec, this, new MessageLog(), monitor); - return loaded.getDomainObject(); + try (LoadResults loadResults = ProgramLoader.builder() + .source(file) + .project(state.getProject()) + .loaders(BinaryLoader.class) + .language(language) + .compiler(compilerSpec) + .monitor(monitor) + .load()) { + return loadResults.getPrimaryDomainObject(this); } catch (LoadException e) { return null; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/AnalyzeHeadless.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/AnalyzeHeadless.java index d091481c30..45d7358b82 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/AnalyzeHeadless.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/AnalyzeHeadless.java @@ -45,8 +45,8 @@ public class AnalyzeHeadless implements GhidraLaunchable { //@formatter:off IMPORT("-import", true, "[|]+"), PROCESS("-process", true, "[]"), - PRE_SCRIPT("-prescript", true, ""), - POST_SCRIPT("-postscript", true, ""), + PRE_SCRIPT("-preScript", true, ""), + POST_SCRIPT("-postScript", true, ""), SCRIPT_PATH("-scriptPath", true, "\"[;...]\""), PROPERTIES_PATH("-propertiesPath", true, "\"[;...]\""), SCRIPT_LOG("-scriptlog", true, ""), @@ -54,7 +54,7 @@ public class AnalyzeHeadless implements GhidraLaunchable { OVERWRITE("-overwrite", false), RECURSIVE("-recursive", false), READ_ONLY("-readOnly", false), - DELETE_PROJECT("-deleteproject", false), + DELETE_PROJECT("-deleteProject", false), NO_ANALYSIS("-noanalysis", false), PROCESSOR("-processor", true, ""), CSPEC("-cspec", true, ""), @@ -66,7 +66,7 @@ public class AnalyzeHeadless implements GhidraLaunchable { OK_TO_DELETE("-okToDelete", false), MAX_CPU("-max-cpu", true, ""), LIBRARY_SEARCH_PATHS("-librarySearchPaths", true, "[;...]"), - LOADER("-loader", true, ""), + LOADER(Loader.COMMAND_LINE_ARG_PREFIX, true, ""), LOADER_ARGS(Loader.COMMAND_LINE_ARG_PREFIX + "-", true, "") { @Override public boolean matches(String arg) { @@ -424,7 +424,8 @@ public class AnalyzeHeadless implements GhidraLaunchable { options.setPostScriptsWithArgs(postScripts); // Set loader and loader args - options.setLoader(loaderName, loaderArgs); + options.setLoader(loaderName); + options.setLoaderArgs(loaderArgs); // Set user-specified language and compiler spec options.setLanguageAndCompiler(languageId, compilerSpecId); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java index d1d69c923f..1fd8f414c4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -31,8 +31,7 @@ import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.script.*; import ghidra.app.util.headless.HeadlessScript.HeadlessContinuationOption; -import ghidra.app.util.importer.AutoImporter; -import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.importer.ProgramLoader; import ghidra.app.util.opinion.*; import ghidra.formats.gfilesystem.*; import ghidra.framework.*; @@ -1529,17 +1528,23 @@ public class HeadlessAnalyzer { Msg.info(this, "IMPORTING: " + fsrl); - LoadResults loadResults = null; - Loaded primary = null; - try { + // Perform the load. Note that loading 1 file may result in more than 1 thing getting + // loaded. + Program primaryProgram = null; + try (LoadResults loadResults = ProgramLoader.builder() + .source(fsrl) + .project(project) + .projectFolderPath(folderPath) + .language(options.language) + .compiler(options.compilerSpec) + .loaders(options.loaderClass) + .loaderArgs(options.loaderArgs) + .load()) { - // Perform the load. Note that loading 1 file may result in more than 1 thing getting - // loaded. - loadResults = loadPrograms(fsrl, folderPath); Msg.info(this, "IMPORTING: Loaded " + (loadResults.size() - 1) + " additional files"); - primary = loadResults.getPrimary(); - Program primaryProgram = primary.getDomainObject(); + Loaded primary = loadResults.getPrimary(); + primaryProgram = primary.getDomainObject(this); // Make sure we are allowed to save ALL programs to the project. If not, save none and // fail. @@ -1565,7 +1570,15 @@ public class HeadlessAnalyzer { // The act of marking the program as temporary by a script will signal // us to discard any changes if (!doSave) { - loadResults.forEach(e -> e.getDomainObject().setTemporary(true)); + for (Loaded loaded : loadResults) { + Program program = loaded.getDomainObject(this); + try { + program.setTemporary(true); + } + finally { + program.release(this); + } + } } // Apply saveDomainFolder to the primary program, if applicable. @@ -1581,38 +1594,48 @@ public class HeadlessAnalyzer { // Save for (Loaded loaded : loadResults) { - if (!loaded.getDomainObject().isTemporary()) { - try { - DomainFile domainFile = - loaded.save(project, new MessageLog(), TaskMonitor.DUMMY); - Msg.info(this, String.format("REPORT: Save succeeded for: %s (%s)", loaded, - domainFile)); - } - catch (IOException e) { - Msg.info(this, "REPORT: Save failed for: " + loaded); - } - } - else { - if (options.readOnly) { - Msg.info(this, - "REPORT: Discarded file import due to readOnly option: " + loaded); + Program program = loaded.getDomainObject(this); + try { + if (!program.isTemporary()) { + try { + DomainFile domainFile = loaded.save(TaskMonitor.DUMMY); + Msg.info(this, String.format("REPORT: Save succeeded for: %s (%s)", + loaded, domainFile)); + } + catch (IOException e) { + Msg.info(this, "REPORT: Save failed for: " + loaded); + } } else { - Msg.info(this, "REPORT: Discarded file import as a result of script " + - "activity or analysis timeout: " + loaded); + if (options.readOnly) { + Msg.info(this, + "REPORT: Discarded file import due to readOnly option: " + loaded); + } + else { + Msg.info(this, "REPORT: Discarded file import as a result of script " + + "activity or analysis timeout: " + loaded); + } } } + finally { + program.release(this); + } } // Commit changes if (options.commit) { for (Loaded loaded : loadResults) { - if (!loaded.getDomainObject().isTemporary()) { - if (loaded == primary) { - AutoAnalysisManager.getAnalysisManager(primaryProgram).dispose(); + Program program = loaded.getDomainObject(this); + try { + if (!program.isTemporary()) { + if (loaded == primary) { + AutoAnalysisManager.getAnalysisManager(primaryProgram).dispose(); + } + commitProgram(loaded.getSavedDomainFile()); } - loaded.release(this); - commitProgram(loaded.getSavedDomainFile()); + } + finally { + program.release(this); } } } @@ -1621,7 +1644,7 @@ public class HeadlessAnalyzer { return true; } catch (LoadException e) { - Msg.error(this, "The AutoImporter could not successfully load " + fsrl + + Msg.error(this, "The ProgramLoader could not successfully load " + fsrl + " with the provided import parameters. Please ensure that any specified" + " processor/cspec arguments are compatible with the loader that is used during" + " import and try again."); @@ -1638,37 +1661,12 @@ public class HeadlessAnalyzer { return false; } finally { - if (loadResults != null) { - loadResults.release(this); + if (primaryProgram != null) { + primaryProgram.release(this); } } } - private LoadResults loadPrograms(FSRL fsrl, String folderPath) - throws VersionException, InvalidNameException, DuplicateNameException, - CancelledException, IOException, LoadException { - MessageLog messageLog = new MessageLog(); - - if (options.loaderClass == null) { - // User did not specify a loader - if (options.language == null) { - return AutoImporter.importByUsingBestGuess(fsrl, project, folderPath, this, - messageLog, TaskMonitor.DUMMY); - } - return AutoImporter.importByLookingForLcs(fsrl, project, folderPath, options.language, - options.compilerSpec, this, messageLog, TaskMonitor.DUMMY); - } - - // User specified a loader - if (options.language == null) { - return AutoImporter.importByUsingSpecificLoaderClass(fsrl, project, folderPath, - options.loaderClass, options.loaderArgs, this, messageLog, TaskMonitor.DUMMY); - } - return AutoImporter.importByUsingSpecificLoaderClassAndLcs(fsrl, project, folderPath, - options.loaderClass, options.loaderArgs, options.language, options.compilerSpec, this, - messageLog, TaskMonitor.DUMMY); - } - private void processWithImport(FSRL fsrl, String folderPath, Integer depth, boolean isFirstTime) throws IOException { try (RefdFile refdFile = fsService.getRefdFile(fsrl, TaskMonitor.DUMMY)) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessOptions.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessOptions.java index bfeb35e8fb..4d6dfe99d5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessOptions.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessOptions.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -490,30 +490,29 @@ public class HeadlessOptions { /** * Sets the loader to use for imports, as well as any loader-specific arguments. A null loader - * will attempt "best-guess" if possible. Loader arguments are not supported if a "best-guess" - * is made. + * name will attempt a "best-guess" if possible. * - * @param loaderName The name (simple class name) of the loader to use. - * @param loaderArgs A list of loader-specific arguments. Could be null if there are none. - * @throws InvalidInputException if an invalid loader name was specified, or if loader arguments - * were specified but a loader was not. + * @param loaderName The name (simple class name) of the loader to use (could be null) + * @throws InvalidInputException if an invalid loader name was specified */ - public void setLoader(String loaderName, List> loaderArgs) - throws InvalidInputException { + public void setLoader(String loaderName) throws InvalidInputException { if (loaderName != null) { this.loaderClass = LoaderService.getLoaderClassByName(loaderName); if (this.loaderClass == null) { throw new InvalidInputException("Invalid loader name specified: " + loaderName); } - this.loaderArgs = loaderArgs; } else { - if (loaderArgs != null && loaderArgs.size() > 0) { - throw new InvalidInputException( - "Loader arguments defined without a loader being specified."); - } this.loaderClass = null; - this.loaderArgs = null; } } + + /** + * Sets the loader arguments + * + * @param loaderArgs A list of loader-specific arguments. Could be null if there are none. + */ + public void setLoaderArgs(List> loaderArgs) { + this.loaderArgs = loaderArgs; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/AutoImporter.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/AutoImporter.java index 068962be54..7da9ac1d58 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/AutoImporter.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/AutoImporter.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,28 +17,27 @@ package ghidra.app.util.importer; import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.List; import java.util.function.Predicate; import generic.stl.Pair; -import ghidra.app.util.Option; import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.opinion.*; import ghidra.formats.gfilesystem.FSRL; -import ghidra.formats.gfilesystem.FileSystemService; import ghidra.framework.model.*; -import ghidra.program.model.address.AddressFactory; -import ghidra.program.model.lang.*; +import ghidra.program.model.lang.CompilerSpec; +import ghidra.program.model.lang.Language; import ghidra.program.model.listing.Program; -import ghidra.program.util.DefaultLanguageService; import ghidra.util.InvalidNameException; -import ghidra.util.Msg; import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; /** * Utility methods to do {@link Program} imports automatically (without requiring user interaction) + * + * @deprecated Use {@link ProgramLoader} */ +@Deprecated(since = "11.5", forRemoval = true) public final class AutoImporter { private AutoImporter() { // service class; cannot instantiate @@ -50,10 +49,10 @@ public final class AutoImporter { *

* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * saved to a project. That is the responsibility of the caller (see - * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). + * {@link LoadResults#save(TaskMonitor)}). *

- * It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. + * It is also the responsibility of the caller to close the returned {@link Loaded} + * {@link Program}s with {@link LoadResults#close()} when they are no longer needed. * * @param file The {@link File} to import * @param project The {@link Project}. Loaders can use this to take advantage of existing @@ -64,10 +63,12 @@ public final class AutoImporter { * reserves the right to change it for each {@link Loaded} result. The {@link Loaded} results * should be queried for their true project folder paths using * {@link Loaded#getProjectFolderPath()}. - * @param consumer A consumer + * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used + * to ensure the underlying {@link Program}s are only closed when every consumer is done + * with it (see {@link LoadResults#close()}). * @param messageLog The log * @param monitor A task monitor - * @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s + * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s * (created but not saved) * @throws IOException if there was an IO-related problem loading * @throws CancelledException if the operation was cancelled @@ -76,13 +77,20 @@ public final class AutoImporter { * @throws VersionException if there was an issue with database versions, probably due to a * failed language upgrade * @throws LoadException if nothing was loaded + * @deprecated Use {@link ProgramLoader} */ + @Deprecated(since = "11.5", forRemoval = true) public static LoadResults importByUsingBestGuess(File file, Project project, String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException, LoadException { - return importByUsingBestGuess(fileToFsrl(file), project, projectFolderPath, consumer, - messageLog, monitor); + return ProgramLoader.builder() + .source(file) + .project(project) + .projectFolderPath(projectFolderPath) + .log(messageLog) + .monitor(monitor) + .load(consumer); } /** @@ -91,10 +99,10 @@ public final class AutoImporter { *

* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * saved to a project. That is the responsibility of the caller (see - * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). + * {@link LoadResults#save(TaskMonitor)}). *

- * It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. + * It is also the responsibility of the caller to close the returned {@link Loaded} + * {@link Program}s with {@link LoadResults#close()} when they are no longer needed. * * @param fsrl The {@link FSRL} to import * @param project The {@link Project}. Loaders can use this to take advantage of existing @@ -105,10 +113,12 @@ public final class AutoImporter { * reserves the right to change it for each {@link Loaded} result. The {@link Loaded} results * should be queried for their true project folder paths using * {@link Loaded#getProjectFolderPath()}. - * @param consumer A consumer + * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used + * to ensure the underlying {@link Program}s are only closed when every consumer is done + * with it (see {@link LoadResults#close()}). * @param messageLog The log * @param monitor A task monitor - * @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s + * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s * (created but not saved) * @throws IOException if there was an IO-related problem loading * @throws CancelledException if the operation was cancelled @@ -117,14 +127,20 @@ public final class AutoImporter { * @throws VersionException if there was an issue with database versions, probably due to a * failed language upgrade * @throws LoadException if nothing was loaded + * @deprecated Use {@link ProgramLoader} */ + @Deprecated(since = "11.5", forRemoval = true) public static LoadResults importByUsingBestGuess(FSRL fsrl, Project project, String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException, LoadException { - return importFresh(fsrl, project, projectFolderPath, consumer, messageLog, monitor, - LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null, - OptionChooser.DEFAULT_OPTIONS); + return ProgramLoader.builder() + .source(fsrl) + .project(project) + .projectFolderPath(projectFolderPath) + .log(messageLog) + .monitor(monitor) + .load(consumer); } /** @@ -133,10 +149,10 @@ public final class AutoImporter { *

* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * saved to a project. That is the responsibility of the caller (see - * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). + * {@link LoadResults#save(TaskMonitor)}). *

- * It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. + * It is also the responsibility of the caller to close the returned {@link Loaded} + * {@link Program}s with {@link LoadResults#close()} when they are no longer needed. * * @param provider The bytes to import * @param project The {@link Project}. Loaders can use this to take advantage of existing @@ -147,10 +163,12 @@ public final class AutoImporter { * reserves the right to change it for each {@link Loaded} result. The {@link Loaded} results * should be queried for their true project folder paths using * {@link Loaded#getProjectFolderPath()}. - * @param consumer A consumer + * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used + * to ensure the underlying {@link Program}s are only closed when every consumer is done + * with it (see {@link LoadResults#close()}). * @param messageLog The log * @param monitor A task monitor - * @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s + * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s * (created but not saved) * @throws IOException if there was an IO-related problem loading * @throws CancelledException if the operation was cancelled @@ -159,14 +177,20 @@ public final class AutoImporter { * @throws VersionException if there was an issue with database versions, probably due to a * failed language upgrade * @throws LoadException if nothing was loaded + * @deprecated Use {@link ProgramLoader} */ + @Deprecated(since = "11.5", forRemoval = true) public static LoadResults importByUsingBestGuess(ByteProvider provider, Project project, String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException, LoadException { - return importFresh(provider, project, projectFolderPath, consumer, messageLog, monitor, - LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null, - OptionChooser.DEFAULT_OPTIONS); + return ProgramLoader.builder() + .source(provider) + .project(project) + .projectFolderPath(projectFolderPath) + .log(messageLog) + .monitor(monitor) + .load(consumer); } /** @@ -174,10 +198,10 @@ public final class AutoImporter { *

* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * saved to a project. That is the responsibility of the caller (see - * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). + * {@link LoadResults#save(TaskMonitor)}). *

- * It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. + * It is also the responsibility of the caller to close the returned {@link Loaded} + * {@link Program}s with {@link LoadResults#close()} when they are no longer needed. * * @param file The {@link File} to import * @param project The {@link Project}. Loaders can use this to take advantage of existing @@ -190,10 +214,12 @@ public final class AutoImporter { * {@link Loaded#getProjectFolderPath()}. * @param loaderClass The {@link Loader} class to use * @param loaderArgs A {@link List} of optional {@link Loader}-specific arguments - * @param consumer A consumer + * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used + * to ensure the underlying {@link Program}s are only closed when every consumer is done + * with it (see {@link LoadResults#close()}). * @param messageLog The log * @param monitor A task monitor - * @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s + * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s * (created but not saved) * @throws IOException if there was an IO-related problem loading * @throws CancelledException if the operation was cancelled @@ -202,14 +228,23 @@ public final class AutoImporter { * @throws VersionException if there was an issue with database versions, probably due to a * failed language upgrade * @throws LoadException if nothing was loaded + * @deprecated Use {@link ProgramLoader} */ + @Deprecated(since = "11.5", forRemoval = true) public static LoadResults importByUsingSpecificLoaderClass(File file, Project project, String projectFolderPath, Class loaderClass, List> loaderArgs, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException, LoadException { - return importByUsingSpecificLoaderClass(fileToFsrl(file), project, projectFolderPath, - loaderClass, loaderArgs, consumer, messageLog, monitor); + return ProgramLoader.builder() + .source(file) + .project(project) + .projectFolderPath(projectFolderPath) + .loaders(loaderClass) + .loaderArgs(loaderArgs) + .log(messageLog) + .monitor(monitor) + .load(consumer); } /** @@ -217,10 +252,10 @@ public final class AutoImporter { *

* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * saved to a project. That is the responsibility of the caller (see - * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). + * {@link LoadResults#save(TaskMonitor)}). *

- * It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. + * It is also the responsibility of the caller to close the returned {@link Loaded} + * {@link Program}s with {@link LoadResults#close()} when they are no longer needed. * * @param fsrl The {@link FSRL} to import * @param project The {@link Project}. Loaders can use this to take advantage of existing @@ -233,10 +268,12 @@ public final class AutoImporter { * {@link Loaded#getProjectFolderPath()}. * @param loaderClass The {@link Loader} class to use * @param loaderArgs A {@link List} of optional {@link Loader}-specific arguments - * @param consumer A consumer + * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used + * to ensure the underlying {@link Program}s are only closed when every consumer is done + * with it (see {@link LoadResults#close()}). * @param messageLog The log * @param monitor A task monitor - * @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s + * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s * (created but not saved) * @throws IOException if there was an IO-related problem loading * @throws CancelledException if the operation was cancelled @@ -245,16 +282,23 @@ public final class AutoImporter { * @throws VersionException if there was an issue with database versions, probably due to a * failed language upgrade * @throws LoadException if nothing was loaded + * @deprecated Use {@link ProgramLoader} */ + @Deprecated(since = "11.5", forRemoval = true) public static LoadResults importByUsingSpecificLoaderClass(FSRL fsrl, Project project, String projectFolderPath, Class loaderClass, List> loaderArgs, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException, LoadException { - SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, loaderArgs); - return importFresh(fsrl, project, projectFolderPath, consumer, messageLog, monitor, - loaderFilter, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, null, - new LoaderArgsOptionChooser(loaderFilter)); + return ProgramLoader.builder() + .source(fsrl) + .project(project) + .projectFolderPath(projectFolderPath) + .loaders(loaderClass) + .loaderArgs(loaderArgs) + .log(messageLog) + .monitor(monitor) + .load(consumer); } /** @@ -263,10 +307,10 @@ public final class AutoImporter { *

* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * saved to a project. That is the responsibility of the caller (see - * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). + * {@link LoadResults#save(TaskMonitor)}). *

- * It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. + * It is also the responsibility of the caller to close the returned {@link Loaded} + * {@link Program}s with {@link LoadResults#close()} when they are no longer needed. * * @param file The {@link File} to import * @param project The {@link Project}. Loaders can use this to take advantage of existing @@ -279,10 +323,12 @@ public final class AutoImporter { * {@link Loaded#getProjectFolderPath()}. * @param language The desired {@link Language} * @param compilerSpec The desired {@link CompilerSpec compiler specification} - * @param consumer A consumer + * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used + * to ensure the underlying {@link Program}s are only closed when every consumer is done + * with it (see {@link LoadResults#close()}). * @param messageLog The log * @param monitor A task monitor - * @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s + * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s * (created but not saved) * @throws IOException if there was an IO-related problem loading * @throws CancelledException if the operation was cancelled @@ -291,13 +337,22 @@ public final class AutoImporter { * @throws VersionException if there was an issue with database versions, probably due to a * failed language upgrade * @throws LoadException if nothing was loaded + * @deprecated Use {@link ProgramLoader} */ + @Deprecated(since = "11.5", forRemoval = true) public static LoadResults importByLookingForLcs(File file, Project project, String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException, LoadException { - return importByLookingForLcs(fileToFsrl(file), project, projectFolderPath, language, - compilerSpec, consumer, messageLog, monitor); + return ProgramLoader.builder() + .source(file) + .project(project) + .projectFolderPath(projectFolderPath) + .language(language) + .compiler(compilerSpec) + .log(messageLog) + .monitor(monitor) + .load(consumer); } /** @@ -306,10 +361,10 @@ public final class AutoImporter { *

* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * saved to a project. That is the responsibility of the caller (see - * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). + * {@link LoadResults#save(TaskMonitor)}). *

- * It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. + * It is also the responsibility of the caller to close the returned {@link Loaded} + * {@link Program}s with {@link LoadResults#close()} when they are no longer needed. * * @param fsrl The {@link FSRL} to import * @param project The {@link Project}. Loaders can use this to take advantage of existing @@ -322,10 +377,12 @@ public final class AutoImporter { * {@link Loaded#getProjectFolderPath()}. * @param language The desired {@link Language} * @param compilerSpec The desired {@link CompilerSpec compiler specification} - * @param consumer A consumer + * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used + * to ensure the underlying {@link Program}s are only closed when every consumer is done + * with it (see {@link LoadResults#close()}). * @param messageLog The log * @param monitor A task monitor - * @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s + * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s * (created but not saved) * @throws IOException if there was an IO-related problem loading * @throws CancelledException if the operation was cancelled @@ -334,14 +391,22 @@ public final class AutoImporter { * @throws VersionException if there was an issue with database versions, probably due to a * failed language upgrade * @throws LoadException if nothing was loaded + * @deprecated Use {@link ProgramLoader} */ + @Deprecated(since = "11.5", forRemoval = true) public static LoadResults importByLookingForLcs(FSRL fsrl, Project project, String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException, LoadException { - return importFresh(fsrl, project, projectFolderPath, consumer, messageLog, monitor, - LoaderService.ACCEPT_ALL, new LcsHintLoadSpecChooser(language, compilerSpec), null, - OptionChooser.DEFAULT_OPTIONS); + return ProgramLoader.builder() + .source(fsrl) + .project(project) + .projectFolderPath(projectFolderPath) + .language(language) + .compiler(compilerSpec) + .log(messageLog) + .monitor(monitor) + .load(consumer); } /** @@ -350,10 +415,10 @@ public final class AutoImporter { *

* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * saved to a project. That is the responsibility of the caller (see - * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). + * {@link LoadResults#save(TaskMonitor)}). *

- * It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. + * It is also the responsibility of the caller to close the returned {@link Loaded} + * {@link Program}s with {@link LoadResults#close()} when they are no longer needed. * * @param file The {@link File} to import * @param project The {@link Project}. Loaders can use this to take advantage of existing @@ -368,10 +433,12 @@ public final class AutoImporter { * @param loaderArgs A {@link List} of optional {@link Loader}-specific arguments * @param language The desired {@link Language} * @param compilerSpec The desired {@link CompilerSpec compiler specification} - * @param consumer A consumer + * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used + * to ensure the underlying {@link Program}s are only closed when every consumer is done + * with it (see {@link LoadResults#close()}). * @param messageLog The log * @param monitor A task monitor - * @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s + * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s * (created but not saved) * @throws IOException if there was an IO-related problem loading * @throws CancelledException if the operation was cancelled @@ -379,14 +446,25 @@ public final class AutoImporter { * @throws InvalidNameException if an invalid {@link Program} name was used during load * @throws VersionException if there was an issue with database versions, probably due to a * failed language upgrade + * @deprecated Use {@link ProgramLoader} */ + @Deprecated(since = "11.5", forRemoval = true) public static LoadResults importByUsingSpecificLoaderClassAndLcs(File file, Project project, String projectFolderPath, Class loaderClass, List> loaderArgs, Language language, CompilerSpec compilerSpec, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException { - return importByUsingSpecificLoaderClassAndLcs(fileToFsrl(file), project, projectFolderPath, - loaderClass, loaderArgs, language, compilerSpec, consumer, messageLog, monitor); + return ProgramLoader.builder() + .source(file) + .project(project) + .projectFolderPath(projectFolderPath) + .loaders(loaderClass) + .loaderArgs(loaderArgs) + .language(language) + .compiler(compilerSpec) + .log(messageLog) + .monitor(monitor) + .load(consumer); } /** @@ -395,10 +473,10 @@ public final class AutoImporter { *

* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * saved to a project. That is the responsibility of the caller (see - * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). + * {@link LoadResults#save(TaskMonitor)}). *

- * It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. + * It is also the responsibility of the caller to close the returned {@link Loaded} + * {@link Program}s with {@link LoadResults#close()} when they are no longer needed. * * @param fsrl The {@link FSRL} to import * @param project The {@link Project}. Loaders can use this to take advantage of existing @@ -413,10 +491,12 @@ public final class AutoImporter { * @param loaderArgs A {@link List} of optional {@link Loader}-specific arguments * @param language The desired {@link Language} * @param compilerSpec The desired {@link CompilerSpec compiler specification} - * @param consumer A consumer + * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used + * to ensure the underlying {@link Program}s are only closed when every consumer is done + * with it (see {@link LoadResults#close()}). * @param messageLog The log * @param monitor A task monitor - * @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s + * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s * (created but not saved) * @throws IOException if there was an IO-related problem loading * @throws CancelledException if the operation was cancelled @@ -424,31 +504,37 @@ public final class AutoImporter { * @throws InvalidNameException if an invalid {@link Program} name was used during load * @throws VersionException if there was an issue with database versions, probably due to a * failed language upgrade + * @deprecated Use {@link ProgramLoader} */ + @Deprecated(since = "11.5", forRemoval = true) public static LoadResults importByUsingSpecificLoaderClassAndLcs(FSRL fsrl, Project project, String projectFolderPath, Class loaderClass, List> loaderArgs, Language language, CompilerSpec compilerSpec, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException { - SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, loaderArgs); - return importFresh(fsrl, project, projectFolderPath, consumer, messageLog, monitor, - loaderFilter, new LcsHintLoadSpecChooser(language, compilerSpec), null, - new LoaderArgsOptionChooser(loaderFilter)); + return ProgramLoader.builder() + .source(fsrl) + .project(project) + .projectFolderPath(projectFolderPath) + .loaders(loaderClass) + .loaderArgs(loaderArgs) + .language(language) + .compiler(compilerSpec) + .log(messageLog) + .monitor(monitor) + .load(consumer); } - private static final Predicate BINARY_LOADER = - new SingleLoaderFilter(BinaryLoader.class); - /** * Automatically imports the given {@link File} with the {@link BinaryLoader}, using the given * language and compiler specification. *

* Note that when the import completes, the returned {@link Loaded} {@link Program} is * not saved to a project. That is the responsibility of the caller (see - * {@link Loaded#save(Project, MessageLog, TaskMonitor)}). + * {@link Loaded#save(TaskMonitor)}). *

- * It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program} with {@link Loaded#release(Object)} when it is no longer needed. + * It is also the responsibility of the caller to close the returned {@link Loaded} + * {@link Program} with {@link Loaded#close()} when it is no longer needed. * * @param file The {@link File} to import * @param project The {@link Project}. Loaders can use this to take advantage of existing @@ -461,7 +547,9 @@ public final class AutoImporter { * {@link Loaded#getProjectFolderPath()}. * @param language The desired {@link Language} * @param compilerSpec The desired {@link CompilerSpec compiler specification} - * @param consumer A consumer + * @param consumer A reference to the object "consuming" the returned {@link Loaded} + * {@link Program}, used to ensure the underlying {@link Program} is only closed when every + * consumer is done with it (see {@link Loaded#close()}). * @param messageLog The log * @param monitor A task monitor * @return The {@link Loaded} {@link Program} (created but not saved) @@ -472,15 +560,24 @@ public final class AutoImporter { * @throws VersionException if there was an issue with database versions, probably due to a * failed language upgrade * @throws LoadException if nothing was loaded + * @deprecated Use {@link ProgramLoader} */ + @Deprecated(since = "11.5", forRemoval = true) public static Loaded importAsBinary(File file, Project project, String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException, LoadException { - LoadResults loadResults = importFresh(file, project, projectFolderPath, consumer, - messageLog, monitor, BINARY_LOADER, new LcsHintLoadSpecChooser(language, compilerSpec), - null, OptionChooser.DEFAULT_OPTIONS); - loadResults.releaseNonPrimary(consumer); + LoadResults loadResults = ProgramLoader.builder() + .source(file) + .project(project) + .projectFolderPath(projectFolderPath) + .loaders(BinaryLoader.class) + .language(language) + .compiler(compilerSpec) + .log(messageLog) + .monitor(monitor) + .load(consumer); + loadResults.getNonPrimary().forEach(Loaded::close); return loadResults.getPrimary(); } @@ -490,10 +587,10 @@ public final class AutoImporter { *

* Note that when the import completes, the returned {@link Loaded} {@link Program} is * not saved to a project. That is the responsibility of the caller (see - * {@link Loaded#save(Project, MessageLog, TaskMonitor)}). + * {@link Loaded#save(TaskMonitor)}). *

- * It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program} with {@link Loaded#release(Object)} when it is no longer needed. + * It is also the responsibility of the caller to close the returned {@link Loaded} + * {@link Program} with {@link Loaded#close()} when it is no longer needed. * * @param bytes The bytes to import * @param project The {@link Project}. Loaders can use this to take advantage of existing @@ -506,7 +603,9 @@ public final class AutoImporter { * {@link Loaded#getProjectFolderPath()}. * @param language The desired {@link Language} * @param compilerSpec The desired {@link CompilerSpec compiler specification} - * @param consumer A consumer + * @param consumer A reference to the object "consuming" the returned {@link Loaded} + * {@link Program}, used to ensure the underlying {@link Program} is only closed when every + * consumer is done with it (see {@link Loaded#close()}). * @param messageLog The log * @param monitor A task monitor * @return The {@link Loaded} {@link Program} (created but not saved) @@ -517,16 +616,25 @@ public final class AutoImporter { * @throws VersionException if there was an issue with database versions, probably due to a * failed language upgrade * @throws LoadException if nothing was loaded + * @deprecated Use {@link ProgramLoader} */ + @Deprecated(since = "11.5", forRemoval = true) public static Loaded importAsBinary(ByteProvider bytes, Project project, String projectFolderPath, Language language, CompilerSpec compilerSpec, Object consumer, MessageLog messageLog, TaskMonitor monitor) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException, LoadException { - LoadResults loadResults = importFresh(bytes, project, projectFolderPath, consumer, - messageLog, monitor, BINARY_LOADER, new LcsHintLoadSpecChooser(language, compilerSpec), - null, OptionChooser.DEFAULT_OPTIONS); - loadResults.releaseNonPrimary(consumer); + LoadResults loadResults = ProgramLoader.builder() + .source(bytes) + .project(project) + .projectFolderPath(projectFolderPath) + .loaders(BinaryLoader.class) + .language(language) + .compiler(compilerSpec) + .log(messageLog) + .monitor(monitor) + .load(consumer); + loadResults.getNonPrimary().forEach(Loaded::close); return loadResults.getPrimary(); } @@ -535,10 +643,10 @@ public final class AutoImporter { *

* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * saved to a project. That is the responsibility of the caller (see - * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). + * {@link LoadResults#save(TaskMonitor)}). *

* It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. + * {@link Program}s with {@link LoadResults#close()} when they are no longer needed. * * @param file The {@link File} to import * @param project The {@link Project}. Loaders can use this to take advantage of existing @@ -556,10 +664,12 @@ public final class AutoImporter { * {@link Loader}'s preferred name. * @param optionChooser A {@link OptionChooser} used to choose what {@link Loader} options get * used - * @param consumer A consumer + * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used + * to ensure the underlying {@link Program}s are only closed when every consumer is done + * with it (see {@link LoadResults#close()}). * @param messageLog The log * @param monitor A task monitor - * @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s + * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s * (created but not saved) * @throws IOException if there was an IO-related problem loading * @throws CancelledException if the operation was cancelled @@ -568,15 +678,27 @@ public final class AutoImporter { * @throws VersionException if there was an issue with database versions, probably due to a * failed language upgrade * @throws LoadException if nothing was loaded + * @deprecated Use {@link ProgramLoader} */ + @Deprecated(since = "11.5", forRemoval = true) public static LoadResults importFresh(File file, Project project, String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor, Predicate loaderFilter, LoadSpecChooser loadSpecChooser, String importNameOverride, OptionChooser optionChooser) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException, LoadException { - return importFresh(fileToFsrl(file), project, projectFolderPath, consumer, messageLog, - monitor, loaderFilter, loadSpecChooser, importNameOverride, optionChooser); + return ProgramLoader.builder() + .source(file) + .project(project) + .projectFolderPath(projectFolderPath) + .name(importNameOverride) + .loaders(loaderFilter) + .loaderArgs(optionChooser.getArgs()) + .language(loadSpecChooser.getLanguageId()) + .compiler(loadSpecChooser.getCompilerSpecId()) + .log(messageLog) + .monitor(monitor) + .load(consumer); } /** @@ -584,10 +706,10 @@ public final class AutoImporter { *

* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * saved to a project. That is the responsibility of the caller (see - * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). + * {@link LoadResults#save(TaskMonitor)}). *

* It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. + * {@link Program}s with {@link LoadResults#close()} when they are no longer needed. * * @param fsrl The {@link FSRL} to import * @param project The {@link Project}. Loaders can use this to take advantage of existing @@ -605,10 +727,12 @@ public final class AutoImporter { * {@link Loader}'s preferred name. * @param optionChooser A {@link OptionChooser} used to choose what {@link Loader} options get * used - * @param consumer A consumer + * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used + * to ensure the underlying {@link Program}s are only closed when every consumer is done + * with it (see {@link LoadResults#close()}). * @param messageLog The log * @param monitor A task monitor - * @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s + * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s * (created but not saved) * @throws IOException if there was an IO-related problem loading * @throws CancelledException if the operation was cancelled @@ -617,22 +741,27 @@ public final class AutoImporter { * @throws VersionException if there was an issue with database versions, probably due to a * failed language upgrade * @throws LoadException if nothing was loaded + * @deprecated Use {@link ProgramLoader} */ + @Deprecated(since = "11.5", forRemoval = true) public static LoadResults importFresh(FSRL fsrl, Project project, String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor, Predicate loaderFilter, LoadSpecChooser loadSpecChooser, String importNameOverride, OptionChooser optionChooser) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException, LoadException { - if (fsrl == null) { - throw new LoadException("Cannot load null fsrl"); - } - - try (ByteProvider provider = - FileSystemService.getInstance().getByteProvider(fsrl, true, monitor)) { - return importFresh(provider, project, projectFolderPath, consumer, messageLog, monitor, - loaderFilter, loadSpecChooser, importNameOverride, optionChooser); - } + return ProgramLoader.builder() + .source(fsrl) + .project(project) + .projectFolderPath(projectFolderPath) + .name(importNameOverride) + .loaders(loaderFilter) + .loaderArgs(optionChooser.getArgs()) + .language(loadSpecChooser.getLanguageId()) + .compiler(loadSpecChooser.getCompilerSpecId()) + .log(messageLog) + .monitor(monitor) + .load(consumer); } /** @@ -640,10 +769,10 @@ public final class AutoImporter { *

* Note that when the import completes, the returned {@link Loaded} {@link Program}s are not * saved to a project. That is the responsibility of the caller (see - * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). + * {@link LoadResults#save(TaskMonitor)}). *

* It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program}s with {@link LoadResults#release(Object)} when they are no longer needed. + * {@link Program}s with {@link LoadResults#close()} when they are no longer needed. * * @param provider The bytes to import * @param project The {@link Project}. Loaders can use this to take advantage of existing @@ -661,10 +790,12 @@ public final class AutoImporter { * {@link Loader}'s preferred name. * @param optionChooser A {@link OptionChooser} used to choose what {@link Loader} options get * used - * @param consumer A consumer + * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used + * to ensure the underlying {@link Program}s are only closed when every consumer is done + * with it (see {@link LoadResults#close()}). * @param messageLog The log * @param monitor A task monitor - * @return The {@link LoadResults} which contains one ore more {@link Loaded} {@link Program}s + * @return The {@link LoadResults} which contains one or more {@link Loaded} {@link Program}s * (created but not saved) * @throws IOException if there was an IO-related problem loading * @throws CancelledException if the operation was cancelled @@ -673,103 +804,26 @@ public final class AutoImporter { * @throws VersionException if there was an issue with database versions, probably due to a * failed language upgrade * @throws LoadException if nothing was loaded + * @deprecated Use {@link ProgramLoader} */ + @Deprecated(since = "11.5", forRemoval = true) public static LoadResults importFresh(ByteProvider provider, Project project, String projectFolderPath, Object consumer, MessageLog messageLog, TaskMonitor monitor, Predicate loaderFilter, LoadSpecChooser loadSpecChooser, String importNameOverride, OptionChooser optionChooser) throws IOException, CancelledException, DuplicateNameException, InvalidNameException, VersionException, LoadException { - - if (provider == null) { - throw new LoadException("Cannot load null provider"); - } - - // Get the load spec - LoadSpec loadSpec = getLoadSpec(loaderFilter, loadSpecChooser, provider); - if (loadSpec == null) { - throw new LoadException("No load spec found"); - } - - // Get the preferred import name - String importName = loadSpec.getLoader().getPreferredFileName(provider); - if (importNameOverride != null) { - importName = importNameOverride; - } - - // Collect options - LanguageCompilerSpecPair languageCompilerSpecPair = loadSpec.getLanguageCompilerSpec(); - AddressFactory addrFactory = null;// Address type options not permitted if null - if (languageCompilerSpecPair != null) { - // It is assumed that if languageCompilerSpecPair exists, then language will be found - addrFactory = DefaultLanguageService.getLanguageService() - .getLanguage( - languageCompilerSpecPair.languageID) - .getAddressFactory(); - } - List

+ * NOTE: It is assumed that the given {@link LanguageID} is valid and it supports the given + * {@link CompilerSpecID}. + * + * @param languageId The {@link LanguageID} to use (should not be null) + * @param compilerSpecId The {@link CompilerSpecID} to use (if null default compiler spec will + * be used) + * @throws LanguageNotFoundException if there was a problem getting the language + */ + public LcsHintLoadSpecChooser(LanguageID languageId, CompilerSpecID compilerSpecId) + throws LanguageNotFoundException { + this.languageID = languageId; + this.compilerSpecID = Objects.requireNonNullElse(compilerSpecId, + DefaultLanguageService.getLanguageService() + .getLanguage(languageID) + .getDefaultCompilerSpec() + .getCompilerSpecID()); + } + + @Override + public LanguageID getLanguageId() { + return languageID; + } + + @Override + public CompilerSpecID getCompilerSpecId() { + return compilerSpecID; } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LoadSpecChooser.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LoadSpecChooser.java index d7e8d2b78f..8757abae8b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LoadSpecChooser.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LoadSpecChooser.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,6 +16,8 @@ package ghidra.app.util.importer; import ghidra.app.util.opinion.*; +import ghidra.program.model.lang.CompilerSpecID; +import ghidra.program.model.lang.LanguageID; /** * Chooses a {@link LoadSpec} for a {@link Loader} to use based on some criteria @@ -31,6 +33,26 @@ public interface LoadSpecChooser { */ public LoadSpec choose(LoaderMap loaderMap); + /** + * Gets the desired {@link LanguageID} associated with this chooser + * + * @return the desired {@link LanguageID} associated with this chooser, or {@code null} to mean + * "any" + */ + public default LanguageID getLanguageId() { + return null; + } + + /** + * Gets the desired {@link CompilerSpecID} associated with this chooser + * + * @return the desired {@link CompilerSpecID} associated with this chooser, or {@code null} to + * mean "any" + */ + public default CompilerSpecID getCompilerSpecId() { + return null; + } + /** * Chooses the first "preferred" {@link LoadSpec} * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LoaderArgsOptionChooser.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LoaderArgsOptionChooser.java index 6129476c11..db3bcc6330 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LoaderArgsOptionChooser.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/LoaderArgsOptionChooser.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,18 +19,29 @@ import java.util.List; import generic.stl.Pair; import ghidra.app.util.Option; +import ghidra.app.util.opinion.Loader; import ghidra.program.model.address.AddressFactory; import ghidra.util.Msg; /** * An option chooser that applies loader options that were passed in as command line arguments. + * + * @deprecated Use {@link ProgramLoader.Builder#loaderArgs(List)} instead */ +@Deprecated(since = "11.5", forRemoval = true) public class LoaderArgsOptionChooser implements OptionChooser { private List> loaderArgs; - public LoaderArgsOptionChooser(SingleLoaderFilter loaderFilter) { - this.loaderArgs = loaderFilter.getLoaderArgs(); + /** + * Creates a new {@link LoaderArgsOptionChooser} + * + * @param loaderArgs The {@link Loader} arguments + * @deprecated Use {@link ProgramLoader.Builder#loaderArgs(List)} instead + */ + @Deprecated(since = "11.5", forRemoval = true) + public LoaderArgsOptionChooser(List> loaderArgs) { + this.loaderArgs = loaderArgs; } @Override @@ -43,12 +54,12 @@ public class LoaderArgsOptionChooser implements OptionChooser { if (option.getArg() != null && arg.equalsIgnoreCase(option.getArg())) { Object oldVal = option.getValue(); if (option.parseAndSetValueByType(val, addressFactory)) { - Msg.info(AutoImporter.class, String.format( + Msg.info(LoaderArgsOptionChooser.class, String.format( "Successfully applied \"%s\" to \"%s\" (old: \"%s\", new: \"%s\")", arg, option.getName(), oldVal, val)); } else { - Msg.error(AutoImporter.class, String.format( + Msg.error(LoaderArgsOptionChooser.class, String.format( "Failed to apply \"%s\" to \"%s\" (old: \"%s\", bad: \"%s\")", arg, option.getName(), oldVal, val)); return null; @@ -58,11 +69,16 @@ public class LoaderArgsOptionChooser implements OptionChooser { } } if (!foundIt) { - Msg.error(AutoImporter.class, "Loader does not support " + arg + " argument"); - return null; + Msg.warn(LoaderArgsOptionChooser.class, + "Skipping unsupported " + arg + " argument"); } } } return optionChoices; } + + @Override + public List> getArgs() { + return loaderArgs; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/OptionChooser.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/OptionChooser.java index dbd84ed43c..49947adf74 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/OptionChooser.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/importer/OptionChooser.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,11 +17,41 @@ package ghidra.app.util.importer; import java.util.List; +import generic.stl.Pair; import ghidra.app.util.Option; +import ghidra.app.util.opinion.Loader; import ghidra.program.model.address.AddressFactory; +/** + * Chooses which {@link Loader} options to use + * + * @deprecated Use {@link ProgramLoader.Builder#loaderArgs(List)} instead + */ +@Deprecated(since = "11.5", forRemoval = true) @FunctionalInterface public interface OptionChooser { public static final OptionChooser DEFAULT_OPTIONS = (choices, addressFactory) -> choices; + + /** + * Chooses which {@link Loader} options to use + * + * @param optionChoices A {@link List} of available {@link Loader} options + * @param addressFactory The address factory + * @return The {@link List} of {@link Loader} options to use + * @deprecated Use {@link ProgramLoader.Builder#loaderArgs(List)} instead + */ + @Deprecated(since = "11.5", forRemoval = true) List

+ * NOTE: Any previously defined sources will be overwritten. + *

+ * NOTE: Ownership of the given {@link ByteProvider} is not transfered to this + * {@link Builder}, so it is the responsibility of the caller to properly + * {@link ByteProvider#close() close} it when done. + * + * @param p The {@link ByteProvider} to import. A {@code null} value will unset the source. + * @return This {@link Builder} + */ + public Builder source(ByteProvider p) { + this.provider = p; + this.fsrl = null; + this.file = null; + this.bytes = null; + return this; + } + + /** + * Sets the required import source to the given {@link FSRL} + *

+ * NOTE: Any previously defined sources will be overwritten + * + * @param f The {@link FSRL} to import. A {@code null} value will unset the source. + * @return This {@link Builder} + */ + public Builder source(FSRL f) { + this.provider = null; + this.fsrl = f; + this.file = null; + this.bytes = null; + return this; + } + + /** + * Sets the required import source to the given {@link File} + *

+ * NOTE: Any previously defined sources will be overwritten + * + * @param f The {@link File} to import. A {@code null} value will unset the source. + * @return This {@link Builder} + */ + public Builder source(File f) { + this.provider = null; + this.fsrl = null; + this.file = f; + this.bytes = null; + return this; + } + + /** + * Sets the required import source to the given bytes + *

+ * NOTE: Any previously defined sources will be overwritten + * + * @param b The bytes to import. A {@code null} value will unset the source. + * @return This {@link Builder} + */ + public Builder source(byte[] b) { + this.provider = null; + this.fsrl = null; + this.file = null; + this.bytes = b; + return this; + } + + /** + * Sets the {@link Project}. Loaders can use this to take advantage of existing + * {@link DomainFolder}s and {@link DomainFile}s to do custom behaviors such as loading + * libraries. + *

+ * By default, no {@link Project} is associated with the {@link ProgramLoader}. + * + * @param p The {@link Project}. A {@code null} value will unset the project. + * @return This {@link Builder} + */ + public Builder project(Project p) { + this.project = p; + return this; + } + + /** + * Sets the suggested project folder path for the {@link Loaded} {@link Program}s. This is + * just a suggestion, and a {@link Loader} implementation reserves the right to change it + * for each {@link Loaded} result. The {@link Loaded} results should be queried for their + * true project folder paths using {@link Loaded#getProjectFolderPath()}. + *

+ * The default project folder path is the root of the project ({@code "/"}). + * + * @param path The suggested project folder path. A {@code null} value will revert the path + * back to the default value of ({@code "/"}). + * @return This {@link Builder} + */ + public Builder projectFolderPath(String path) { + this.projectFolderPath = path; + return this; + } + + /** + * Sets the name to use for the imported {@link Program}. + *

+ * The default is the {@link Loader}'s preferred name. + * + * @param name The name to use for the imported {@link Program}. A {@code null} value will + * revert the name to the {@link Loader}'s preferred name. + * @return This {@link Builder} + */ + public Builder name(String name) { + this.importNameOverride = name; + return this; + } + + /** + * Sets the acceptable {@link Loader}s to use during import. + *

+ * By default, all {@link Loader}s are accepted ({@link LoaderService#ACCEPT_ALL}). + * + * @param filter A filter used to limit the {@link Loader}s used during import. A + * {@code null} value will revert back to the default ({@link LoaderService#ACCEPT_ALL}). + * @return This {@link Builder} + */ + public Builder loaders(Predicate filter) { + this.loaderFilter = filter != null ? filter : LoaderService.ACCEPT_ALL; + return this; + } + + /** + * Sets the acceptable {@link Loader} to use during import. + *

+ * By default, all {@link Loader}s are accepted ({@link LoaderService#ACCEPT_ALL}). + * + * @param cls The class of the {@link Loader} to use during import. A {@code null} value + * will revert back to the default ({@link LoaderService#ACCEPT_ALL}). + * @return This {@link Builder} + */ + public Builder loaders(Class cls) { + this.loaderFilter = + cls != null ? loader -> loader.getClass().equals(cls) : LoaderService.ACCEPT_ALL; + return this; + } + + /** + * Sets the {@link Loader}s to use during import. + *

+ * By default, all {@link Loader}s are accepted ({@link LoaderService#ACCEPT_ALL}). + * + * @param cls A {@link List} of classes of {@link Loader}s to use during import. A + * {@code null} value will revert back to the default ({@link LoaderService#ACCEPT_ALL}). + * @return This {@link Builder} + */ + public Builder loaders(List> cls) { + this.loaderFilter = + cls != null ? loader -> cls.contains(loader.getClass()) : LoaderService.ACCEPT_ALL; + return this; + } + + /** + * Sets the {@link Loader} arguments to use during import. + *

+ * By default, no {@link Loader} arguments are used. + * + * @param args A {@link List} of {@link Loader} argument name/value {@link Pair}s to use + * during import. A {@code null} value will result in no {@link Loader} arguments being + * used. + * @return This {@link Builder} + */ + public Builder loaderArgs(List> args) { + this.loaderArgs = args != null ? new ArrayList<>(args) : new ArrayList<>(); + return this; + } + + /** + * Adds the given {@link Loader} argument to use during import. + * + * @param name A single {@link Loader} argument name to use during import. + * @param value The value that corresponds to the argument {@code name} + * @return This {@link Builder} + */ + public Builder addLoaderArg(String name, String value) { + this.loaderArgs.add(new Pair(name, value)); + return this; + } + + /** + * Sets the language to use during import. + *

+ * By default, the first "preferred" language is used. + * + * @param id The language id to use during import. A {@code null} value will result in the + * first "preferred" language being used. + * @return This {@link Builder} + */ + public Builder language(String id) { + this.languageId = id != null ? new LanguageID(id) : null; + return this; + } + + /** + * Sets the language to use during import. + *

+ * By default, the first "preferred" language is used. + * + * @param id The {@link LanguageID} to use during import. A {@code null} value will result + * in the first "preferred" language being used. + * @return This {@link Builder} + */ + public Builder language(LanguageID id) { + this.languageId = id; + return this; + } + + /** + * Sets the language to use during import. + *

+ * By default, the first "preferred" language is used. + * + * @param language The {@link Language} to use during import. A {@code null} value will + * result in the first "preferred" language being used. + * @return This {@link Builder} + */ + public Builder language(Language language) { + this.languageId = language != null ? language.getLanguageID() : null; + return this; + } + + /** + * Sets the compiler to use during import. + *

+ * By default, the processor's default compiler is used. + * + * @param id The compiler spec id to use during import. A {@code null} value will result in + * the language's default compiler being used. + * @return This {@link Builder} + */ + public Builder compiler(String id) { + this.compilerSpecId = id != null ? new CompilerSpecID(id) : null; + return this; + } + + /** + * Sets the compiler to use during import. + *

+ * By default, the processor's default compiler is used. + * + * @param id The {@link CompilerSpecID} to use during import. A {@code null} value will + * result in the language's default compiler being used. + * @return This {@link Builder} + */ + public Builder compiler(CompilerSpecID id) { + this.compilerSpecId = id; + return this; + } + + /** + * Sets the compiler to use during import. + *

+ * By default, the processor's default compiler is used. + * + * @param cspec The {@link CompilerSpec} to use during import. A {@code null} value will + * result in the language's default compiler being used. + * @return This {@link Builder} + */ + public Builder compiler(CompilerSpec cspec) { + this.compilerSpecId = cspec != null ? cspec.getCompilerSpecID() : null; + return this; + } + + /** + * Sets the {@link MessageLog log} to use during import. + *

+ * By default, no log is used. + * + * @param messageLog The {@link MessageLog log} to use during import. A {@code null} value + * will result in not logging. + * @return This {@link Builder} + */ + public Builder log(MessageLog messageLog) { + this.log = messageLog; + return this; + } + + /** + * Sets the {@link TaskMonitor} to use during import. + *

+ * By default, {@link TaskMonitor#DUMMY} is used. + * + * @param mon The {@link TaskMonitor} to use during import. A {@code null} value will result + * in {@link TaskMonitor#DUMMY} being used. + * @return This {@link Builder} + */ + public Builder monitor(TaskMonitor mon) { + this.monitor = mon; + return this; + } + + /** + * Loads the specified {@link #source(ByteProvider) source} with this {@link Builder}'s + * current configuration + * + * @return The {@link LoadResults} which contains one or more {@link Loaded} + * {@link Program}s (created but not saved) + * @throws IOException if there was an IO-related problem loading + * @throws LanguageNotFoundException if there was a problem getting the language + * @throws CancelledException if the operation was cancelled + * @throws VersionException if there was an issue with database versions, probably due to a + * failed language upgrade + * @throws LoadException if there was a problem loading + */ + public LoadResults load() throws IOException, LanguageNotFoundException, + CancelledException, VersionException, LoadException { + return load(this); + } + + /** + * Loads the specified {@link #source(ByteProvider) source} with this {@link Builder}'s + * current configuration. + *

+ * NOTE: This method exists to maintain compatibility with the {@link AutoImporter} class, + * whose methods require consumer objects to be passed in. It should not be used by clients + * (use {@link #load()} instead, which uses a built-in consumer). + * + * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, + * used to ensure the underlying {@link Program}s are only closed when every consumer is + * done with it (see {@link LoadResults#close()}). + * @return The {@link LoadResults} which contains one or more {@link Loaded} + * {@link Program}s (created but not saved) + * @throws IOException if there was an IO-related problem loading + * @throws LanguageNotFoundException if there was a problem getting the language + * @throws CancelledException if the operation was cancelled + * @throws VersionException if there was an issue with database versions, probably due to a + * failed language upgrade + * @throws LoadException if there was a problem loading + * @deprecated Use {@link #load()} instead + */ + @SuppressWarnings("unchecked") + @Deprecated(since = "11.5", forRemoval = true) + LoadResults load(Object consumer) throws IOException, LanguageNotFoundException, + CancelledException, VersionException, LoadException { + try (ByteProvider p = getSourceAsProvider()) { + + LoadSpec loadSpec = getLoadSpec(p); + List

+ * NOTE: The returned {@link ByteProvider} should always be + * {@link ByteProvider#close() closed} by the caller. If this {@link Builder}'s source + * originated from a {@link ByteProvider}, the {@link ByteProvider#close()} will be a + * no-op. + * + * @return This {@link Builder}'s source as a {@link Byte Provider} + * @throws IOException if there was an IO-related problem + * @throws LoadException if there was no defined source + * @throws CancelledException if the operation was cancelled + */ + private ByteProvider getSourceAsProvider() + throws IOException, LoadException, CancelledException { + FileSystemService fsService = FileSystemService.getInstance(); + ByteProvider p; + if (provider != null) { + p = new ByteProviderWrapper(provider, provider.getFSRL()); // wrap to prevent closing + } + else if (fsrl != null) { + p = fsService.getByteProvider(fsrl, true, monitor); + } + else if (file != null) { + p = fsService.getByteProvider(fsService.getLocalFSRL(file), true, monitor); + } + else if (bytes != null) { + p = new ByteArrayProvider(bytes); + } + else { + throw new LoadException("No source to import!"); + } + return p; + } + + /** + * Gets the {@link LoadSpec} from the given {@link ByteProvider} + * + * @param p The {@link ByteProvider} + * @return A {@link LoadSpec} + * @throws LanguageNotFoundException if there was a problem getting the language + * @throws LoadException if a {@link LoadSpec} was not found + */ + private LoadSpec getLoadSpec(ByteProvider p) + throws LanguageNotFoundException, LoadException { + LoaderMap loaderMap = LoaderService.getSupportedLoadSpecs(p, loaderFilter); + LoadSpecChooser loadSpecChooser = + languageId != null ? new LcsHintLoadSpecChooser(languageId, compilerSpecId) + : (compilerSpecId != null ? new CsHintLoadSpecChooser(compilerSpecId) + : LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED); + LoadSpec loadSpec = loadSpecChooser.choose(loaderMap); + if (loadSpec == null) { + String name = Objects.requireNonNullElse(p.getName(), "???"); + Msg.info(ProgramLoader.class, "No load spec found for import file: " + name); + throw new LoadException("No load spec found"); + } + return loadSpec; + } + + /** + * Gets the {@link Loader} {@link Option}s, with any loader arguments applied + * + * @param p The {@link ByteProvider} + * @param loadSpec The {@link LoadSpec} + * @return The {@link Loader} {@link Option}s, with any loader arguments applied + * @throws LanguageNotFoundException if there was a problem getting the language + * @throws LoadException if the {@link Loader} had {@code null} options + */ + private List

+ * In addition to loading the given program bytes, this implementation will attempt to locate + * the libraries that the program links to from the + * {@link #LINK_SEARCH_FOLDER_OPTION_NAME project library search folder}, the + * {@link #LIBRARY_DEST_FOLDER_OPTION_NAME library destination folder} and the + * {@link #LOAD_LIBRARY_OPTION_NAME libraries found on disk}. All of these locations are + * controlled by loader options. + *

+ * If the hidden {@link #LOAD_ONLY_LIBRARIES_OPTION_NAME} option is set and the given project + * is not {@code null}, it is assumed that a {@link DomainFile} exists at + * {@code projectFolderPath/loadedName}, is open, and the provider corresponds to its contents. + * If this is the case, the primary (first) {@link Loaded} {@link Program} in the returned list + * will NOT be affected by a {@link LoadResults#save(TaskMonitor)} operation. It will be the + * responsibility of the user to save this open program if desired. + * + * @return A {@link List} of one or more {@link Loaded} {@link Program}s (created but not + * saved). The first element in the {@link List} will the primary program, with the remaining + * elements being any newly loaded libraries. + * @throws LoadException if the load failed in an unexpected way. If the + * {@link #LOAD_ONLY_LIBRARIES_OPTION_NAME} option is set, this exception will be thrown if + * the {@link DomainFile} at {@code projectFolderPath/loadedName} does not correspond to an + * open {@link Program}. + */ @Override protected List> loadProgram(ByteProvider provider, String loadedName, Project project, String projectFolderPath, LoadSpec loadSpec, List

* Note that when the load completes, the returned {@link Loaded} {@link Program}s are not * saved to a project. That is the responsibility of the caller (see - * {@link Loaded#save(Project, MessageLog, TaskMonitor)}). + * {@link Loaded#save(TaskMonitor)}). *

- * It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link Program}s with {@link Loaded#release(Object)} when they are no longer - * needed. + * It is also the responsibility of the caller to close the returned {@link Loaded} + * {@link Program}s with {@link Loaded#close()} when they are no longer needed. * * @param provider The bytes to load. * @param loadedName A suggested name for the primary {@link Loaded} {@link Program}. @@ -135,21 +134,26 @@ public abstract class AbstractProgramLoader implements Loader { try { for (Loaded loadedProgram : loadedPrograms) { monitor.checkCancelled(); - Program program = loadedProgram.getDomainObject(); - applyProcessorLabels(options, program); - program.setEventsEnabled(true); + Program program = loadedProgram.getDomainObject(this); + try { + applyProcessorLabels(options, program); + program.setEventsEnabled(true); + } + finally { + program.release(this); + } } // Subclasses can perform custom post-load fix-ups postLoadProgramFixups(loadedPrograms, project, loadSpec, options, messageLog, monitor); - // Discard unneeded programs + // Discard temporary programs Iterator> iter = loadedPrograms.iterator(); while (iter.hasNext()) { Loaded loaded = iter.next(); - if (loaded.shouldDiscard()) { + if (loaded.check(p -> p.isTemporary())) { iter.remove(); - loaded.release(consumer); + loaded.close(); } } @@ -158,7 +162,7 @@ public abstract class AbstractProgramLoader implements Loader { } finally { if (!success) { - release(loadedPrograms, consumer); + loadedPrograms.forEach(Loaded::close); } postLoadCleanup(success); } @@ -463,18 +467,6 @@ public abstract class AbstractProgramLoader implements Loader { return DefaultLanguageService.getLanguageService(); } - /** - * Releases the given consumer from each of the provided {@link Loaded loaded programs} - * - * @param loadedPrograms A list of {@link Loaded loaded programs} which are no longer being used - * @param consumer The consumer that was marking the {@link Program}s as being used - */ - protected final void release(List> loadedPrograms, Object consumer) { - for (Loaded loadedProgram : loadedPrograms) { - loadedProgram.getDomainObject().release(consumer); - } - } - private void applyProcessorLabels(List

+ * This object needs to be {@link #close() closed} when done with it. * * @param loadedList A {@link List} of {@link Loaded} {@link DomainObject}s * @throws IllegalArgumentException if the provided {@link List} is null or empty @@ -60,32 +61,77 @@ public class LoadResults implements Iterable> * * @param domainObject The loaded {@link DomainObject} * @param name The name of the loaded {@link DomainObject}. If a - * {@link #save(Project, Object, MessageLog, TaskMonitor) save} occurs, this will attempted to - * be used for the resulting {@link DomainFile}'s name. + * {@link #save(TaskMonitor) save} occurs, this will attempted to be used for the resulting + * {@link DomainFile}'s name. + * @param project If not null, the project this will get saved to during a + * {@link #save(TaskMonitor)} operation * @param projectFolderPath The project folder path this will get saved to during a - * {@link #save(Project, Object, MessageLog, TaskMonitor) save} operation. If null or empty, - * the root project folder will be used. + * {@link #save(TaskMonitor) save} operation. If null or empty, the root project folder will + * be used. + * @param consumer A reference to the object "consuming" the returned this + * {@link LoadResults}, used to ensure the underlying {@link DomainObject}s are only closed + * when every consumer is done with it (see {@link #close()}). NOTE: Wrapping a + * {@link DomainObject} in a {@link LoadResults} transfers responsibility of releasing the + * given {@link DomainObject} to this {@link LoadResults}'s {@link #close()} method. */ - public LoadResults(T domainObject, String name, String projectFolderPath) { - this(List.of(new Loaded(domainObject, name, projectFolderPath))); + public LoadResults(T domainObject, String name, Project project, String projectFolderPath, + Object consumer) { + this(List.of(new Loaded(domainObject, name, project, projectFolderPath, consumer))); } /** - * Gets the "primary" {@link Loaded} {@link DomainObject}, who's meaning is defined by each + * Gets the "primary" {@link Loaded} {@link DomainObject}, whose meaning is defined by each * {@link Loader} implementation * * @return The "primary" {@link Loaded} {@link DomainObject} */ public Loaded getPrimary() { - return loadedList.get(0); + return loadedList.getFirst(); } /** - * Gets the "primary" {@link DomainObject}, who's meaning is defined by each {@link Loader} - * implementation + * Gets the "non-primary" {@link Loaded} {@link DomainObject}s, whose meaning is defined by each + * {@link Loader} implementation * + * @return The "non-primary" {@link Loaded} {@link DomainObject}s + */ + public List> getNonPrimary() { + return loadedList.stream().skip(1).toList(); + } + + /** + * Gets the "primary" {@link DomainObject}, whose meaning is defined by each {@link Loader} + * implementation. + *

+ * NOTE: It is the responsibility of the caller to properly + * {@link DomainObject#release(Object) release} it when done. This + * {@link DomainObject#release(Object)} does not replace the requirement to + * {@link #close()} the {@link LoadResults} object when done. + * + * @param consumer A new reference to the object "consuming" the returned {@link DomainObject}, + * used to ensure the underlying {@link DomainObject} is only released when every consumer is + * done with it (see {@link DomainObject#release(Object)}). NOTE: This method adds the given + * consumer to the returned {@link DomainObject}, requiring an explicit + * {@link DomainObject#release(Object)} to be called on the return value (this entire + * {@link LoadResults} must also still be {@link #close() closed}). * @return The "primary" {@link DomainObject} */ + public T getPrimaryDomainObject(Object consumer) { + return loadedList.getFirst().getDomainObject(consumer); + } + + /** + * Gets the "primary" loaded {@link DomainObject}, whose meaning is defined by each + * {@link Loader} implementation. Unsafe resource management is used. Temporarily exists to + * provide backwards compatibility. + * + * @return The "primary" {@link DomainObject} + * @deprecated This class's internal {@link DomainObject}s are now cleaned up with the + * {@link #close()} method. If the primary {@link DomainObject} needs to be retrieved from + * this class, instead use {@link #getPrimaryDomainObject(Object)} and independently clean up + * the new reference with a separate call to {@link DomainObject#release(Object)}. + */ + @Deprecated(since = "11.5", forRemoval = true) public T getPrimaryDomainObject() { return loadedList.get(0).getDomainObject(); } @@ -101,65 +147,44 @@ public class LoadResults implements Iterable> } /** - * {@link Loaded#save(Project, MessageLog, TaskMonitor) Saves} each {@link Loaded} - * {@link DomainObject} to the given {@link Project}. - *

- * NOTE: If any fail to save, none will be saved (already saved {@link DomainFile}s will be - * cleaned up/deleted), and all {@link Loaded} {@link DomainObject}s will have been - * {@link #release(Object) released}. + * {@link Loaded#save(TaskMonitor) Saves} each {@link Loaded} {@link DomainObject} to the given + * {@link Project}. * - * @param project The {@link Project} to save to - * @param consumer the consumer - * @param messageLog The log * @param monitor A cancelable task monitor * @throws CancelledException if the operation was cancelled - * @throws IOException If there was a problem saving - * @see Loaded#save(Project, MessageLog, TaskMonitor) + * @throws IOException If there was a problem saving. A thrown exception may result in only some + * of the {@link Loaded} elements being saved. It is the responsibility of the caller to clean + * things up appropriately. + * @see Loaded#save(TaskMonitor) */ - public void save(Project project, Object consumer, MessageLog messageLog, TaskMonitor monitor) - throws CancelledException, IOException { - boolean success = false; - try { - for (Loaded loaded : loadedList) { - loaded.save(project, messageLog, monitor); - } - success = true; - } - finally { - if (!success) { - for (Loaded loaded : this) { - try { - loaded.release(consumer); - loaded.deleteSavedDomainFile(consumer); - } - catch (IOException e1) { - Msg.error(getClass(), "Failed to delete: " + loaded); - } - } - } + public void save(TaskMonitor monitor) throws CancelledException, IOException { + for (Loaded loaded : loadedList) { + loaded.save(monitor); } } /** - * Notify all of the {@link Loaded} {@link DomainObject}s that the specified consumer is no - * longer using them. When the last consumer invokes this method, the {@link Loaded} - * {@link DomainObject}s will be closed and will become invalid. + * Unsafely notifies all of the {@link Loaded} {@link DomainObject}s that the specified consumer + * is no longer using them. Temporarily exists to provide backwards compatibility. * * @param consumer the consumer + * @deprecated Use {@link #close()} instead */ + @Deprecated(since = "11.5", forRemoval = true) public void release(Object consumer) { loadedList.forEach(loaded -> loaded.release(consumer)); } /** - * Notify the filtered {@link Loaded} {@link DomainObject}s that the specified consumer is no - * longer using them. When the last consumer invokes this method, the filtered {@link Loaded} - * {@link DomainObject}s will be closed and will become invalid. + * Unsafely notifies the filtered {@link Loaded} {@link DomainObject}s that the specified + * consumer is no longer using them. Temporarily exists to provide backwards compatibility. * * @param consumer the consumer * @param filter a filter to apply to the {@link Loaded} {@link DomainObject}s prior to the * release + * @deprecated Use {@link #close()} instead */ + @Deprecated(since = "11.5", forRemoval = true) public void release(Object consumer, Predicate> filter) { loadedList.stream().filter(filter).forEach(loaded -> loaded.release(consumer)); } @@ -170,7 +195,10 @@ public class LoadResults implements Iterable> * {@link DomainObject}s will be closed and will become invalid. * * @param consumer the consumer + * @deprecated Use {@link #getNonPrimary()} and {@link Loaded#close()} on the {@link List} + * elements instead */ + @Deprecated(since = "11.5", forRemoval = true) public void releaseNonPrimary(Object consumer) { for (int i = 0; i < loadedList.size(); i++) { if (i > 0) { @@ -179,6 +207,18 @@ public class LoadResults implements Iterable> } } + /** + * Closes this {@link LoadResults} and releases the reference on the object consuming it. + *

+ * NOTE: Any {@link DomainObject}s obtained via {@link #getPrimaryDomainObject(Object)} must + * still be explicitly {@link DomainObject#release(Object) released} after calling this method, + * since they were obtained with their own consumers. + */ + @Override + public void close() { + loadedList.forEach(Loaded::close); + } + @Override public Iterator> iterator() { return loadedList.iterator(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loaded.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loaded.java index eefdd5a439..cbe755a759 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loaded.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loaded.java @@ -17,8 +17,9 @@ package ghidra.app.util.opinion; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.function.Consumer; +import java.util.function.Predicate; -import ghidra.app.util.importer.MessageLog; import ghidra.framework.model.*; import ghidra.util.InvalidNameException; import ghidra.util.exception.*; @@ -28,65 +29,116 @@ import ghidra.util.task.TaskMonitor; * A loaded {@link DomainObject} produced by a {@link Loader}. In addition to storing the loaded * {@link DomainObject}, it also stores the {@link Loader}'s desired name and project folder path * for the loaded {@link DomainObject}, should it get saved to a project. - *

- * NOTE: If an object of this type is marked as {@link #setDiscard(boolean) discardable}, it should - * be {@link #release(Object) released} and not saved. * * @param The type of {@link DomainObject} that was loaded */ -public class Loaded { +public class Loaded implements AutoCloseable { - private final T domainObject; - private final String name; - private String projectFolderPath; + protected final T domainObject; + protected final String name; + protected Project project; + protected String projectFolderPath; + protected Object loadedConsumer; - private DomainFile domainFile; - private boolean ignoreSave; - private boolean discard; + protected DomainFile domainFile; /** - * Creates a new {@link Loaded} object + * Creates a new {@link Loaded} object. + *

+ * This object needs to be {@link #close() closed} when done with it. * * @param domainObject The loaded {@link DomainObject} - * @param name The name of the loaded {@link DomainObject}. If a - * {@link #save(Project, MessageLog, TaskMonitor)} occurs, this will attempted to be used for - * the resulting {@link DomainFile}'s name. + * @param name The name of the loaded {@link DomainObject}. If a {@link #save(TaskMonitor)} + * occurs, this will attempted to be used for the resulting {@link DomainFile}'s name. + * @param project If not null, the project this will get saved to during a + * {@link #save(TaskMonitor)} operation * @param projectFolderPath The project folder path this will get saved to during a - * {@link #save(Project, MessageLog, TaskMonitor)} operation. If null or empty, the root - * project folder will be used. + * {@link #save(TaskMonitor)} operation. If null or empty, the root project folder will be + * used. + * @param consumer A reference to the object "consuming" the returned {@link Loaded} + * {@link DomainObject}, used to ensure the underlying {@link DomainObject} is only closed + * when every consumer is done with it (see {@link #close()}). NOTE: Wrapping a + * {@link DomainObject} in a {@link Loaded} transfers responsibility of releasing the + * given {@link DomainObject} to this {@link Loaded}'s {@link #close()} method. */ - public Loaded(T domainObject, String name, String projectFolderPath) { + public Loaded(T domainObject, String name, Project project, String projectFolderPath, + Object consumer) { this.domainObject = domainObject; this.name = name; + this.project = project; + this.loadedConsumer = consumer; setProjectFolderPath(projectFolderPath); } /** - * Creates a {@link Loaded} view on an existing {@link DomainFile}. This type of {@link Loaded} - * object cannot be saved. - * - * @param domainObject The loaded {@link DomainObject} - * @param domainFile The {@link DomainFile} to be loaded - */ - public Loaded(T domainObject, DomainFile domainFile) { - this(domainObject, domainFile.getName(), domainFile.getParent().getPathname()); - this.domainFile = domainFile; - this.ignoreSave = true; - } - - /** - * Gets the loaded {@link DomainObject} + * Gets the loaded {@link DomainObject}. + *

+ * NOTE: The given It is the responsibility of the caller to properly + * {@link DomainObject#release(Object) release} it when done. This + * {@link DomainObject#release(Object)} does not replace the requirement to + * {@link #close()} the {@link Loaded} object when done. * + * @param consumer A new reference to the object "consuming" the returned {@link DomainObject}, + * used to ensure the underlying {@link DomainObject} is only released when every consumer is + * done with it (see {@link DomainObject#release(Object)}). NOTE: This method adds the given + * consumer to the returned {@link DomainObject}, requiring an explicit + * {@link DomainObject#release(Object)} to be called on the return value (this + * {@link Loaded} must also still be {@link #close() closed}). * @return The loaded {@link DomainObject} */ + public T getDomainObject(Object consumer) { + domainObject.addConsumer(consumer); + return domainObject; + } + + /** + * Gets the loaded {@link DomainObject} with unsafe resource management. Temporarily exists + * to provide backwards compatibility. + * + * @return The loaded {@link DomainObject} + * @deprecated This class's internal {@link DomainObject} is now cleaned up with the + * {@link #close()} method. If the {@link DomainObject} needs to be retrieved from this + * class, instead use {@link #getDomainObject(Object)} and independently clean up the new + * reference with a separate call to {@link DomainObject#release(Object)}. + */ + @Deprecated(since = "11.5", forRemoval = true) public T getDomainObject() { return domainObject; } /** - * Gets the name of the loaded {@link DomainObject}. If a - * {@link #save(Project, MessageLog, TaskMonitor)} occurs, this will attempted to be used for - * the resulting {@link DomainFile}'s name. + * Gets the loaded {@link DomainObject}'s type + * + * @return the loaded {@link DomainObject}'s type + */ + public Class getDomainObjectType() { + return domainObject.getClass(); + } + + /** + * Safely applies the given operation to the loaded {@link DomainObject} without the need to + * worry about resource management + * + * @param operation The operation to apply to the loaded {@link DomainObject} + */ + public void apply(Consumer operation) { + operation.accept(domainObject); + } + + /** + * Safely tests the given predicate on the loaded {@link DomainObject} without the need to + * worry about resource management + * + * @param predicate The predicate to test + * @return The result of the test + */ + public boolean check(Predicate predicate) { + return predicate.test(domainObject); + } + + /** + * Gets the name of the loaded {@link DomainObject}. If a {@link #save(TaskMonitor)} occurs, + * this will attempted to be used for the resulting {@link DomainFile}'s name. * * @return the name of the loaded {@link DomainObject} */ @@ -95,8 +147,18 @@ public class Loaded { } /** - * Gets the project folder path this will get saved to during a - * {@link #save(Project, MessageLog, TaskMonitor)} operation. + * Gets the {@link Project} this will get saved to during a {@link #save(TaskMonitor)} operation + * + *@return The {@link Project} this will get saved to during a {@link #save(TaskMonitor)} + * operation (could be null) + */ + public Project getProject() { + return project; + } + + /** + * Gets the project folder path this will get saved to during a {@link #save(TaskMonitor)} + * operation. *

* NOTE: The returned path will always end with a "/". * @@ -107,12 +169,12 @@ public class Loaded { } /** - * Sets the project folder path this will get saved to during a - * {@link #save(Project, MessageLog, TaskMonitor)} operation. + * Sets the project folder path this will get saved to during a {@link #save(TaskMonitor)} + * operation. * * @param projectFolderPath The project folder path this will get saved to during a - * {@link #save(Project, MessageLog, TaskMonitor)} operation. If null or empty, the root - * project folder will be used. + * {@link #save(TaskMonitor)} operation. If null or empty, the root project folder will be + * used. */ public void setProjectFolderPath(String projectFolderPath) { if (projectFolderPath == null || projectFolderPath.isBlank()) { @@ -124,19 +186,6 @@ public class Loaded { this.projectFolderPath = projectFolderPath; } - /** - * Notify the loaded {@link DomainObject} that the specified consumer is no longer using it. - * When the last consumer invokes this method, the loaded {@link DomainObject} will be closed - * and will become invalid. - * - * @param consumer the consumer - */ - public void release(Object consumer) { - if (!domainObject.isClosed() && domainObject.isUsedBy(consumer)) { - domainObject.release(consumer); - } - } - /** * Saves the loaded {@link DomainObject} to the given {@link Project} at this object's * project folder path, using this object's name. @@ -146,8 +195,6 @@ public class Loaded { * Therefore, it should not be assumed that the returned {@link DomainFile} will have the same * name as a call to {@link #getName()}. * - * @param project The {@link Project} to save to - * @param messageLog The log * @param monitor A cancelable task monitor * @return The {@link DomainFile} where the save happened * @throws CancelledException if the operation was cancelled @@ -155,13 +202,9 @@ public class Loaded { * @throws IOException If there was an IO-related error, an invalid name was specified, or it * was already successfully saved and still exists */ - public DomainFile save(Project project, MessageLog messageLog, TaskMonitor monitor) + public DomainFile save(TaskMonitor monitor) throws CancelledException, ClosedException, IOException { - if (ignoreSave) { - return domainFile; - } - if (domainObject.isClosed()) { throw new ClosedException( "Cannot saved closed DomainObject: " + domainObject.getName()); @@ -202,13 +245,13 @@ public class Loaded { /** * Gets the loaded {@link DomainObject}'s associated {@link DomainFile} that was - * {@link #save(Project, MessageLog, TaskMonitor) saved} + * {@link #save(TaskMonitor) saved} * * @return The loaded {@link DomainObject}'s associated saved {@link DomainFile}, or null if * was not saved * @throws FileNotFoundException If the loaded {@link DomainObject} was saved but the associated * {@link DomainFile} no longer exists - * @see #save(Project, MessageLog, TaskMonitor) + * @see #save(TaskMonitor) */ public DomainFile getSavedDomainFile() throws FileNotFoundException { if (domainFile != null && !domainFile.exists()) { @@ -218,42 +261,32 @@ public class Loaded { } /** - * Checks to see if this {@link Loaded} {@link DomainObject} should be discarded (not saved) - * - * @return True if this {@link Loaded} {@link DomainObject} should be discarded; otherwise, - * false - */ - public boolean shouldDiscard() { - return discard; - } - - /** - * Sets whether or not this {@link Loaded} {@link DomainObject} should be discarded (not saved) - * - * @param discard True if this {@link Loaded} {@link DomainObject} should be discarded; - * otherwise, false - */ - public void setDiscard(boolean discard) { - this.discard = discard; - } - - - /** - * Deletes the loaded {@link DomainObject}'s associated {@link DomainFile} that was - * {@link #save(Project, MessageLog, TaskMonitor) saved}. This method has no effect if it was - * never saved. - *

- * NOTE: The loaded {@link DomainObject} must be {@link #release(Object) released} prior to - * calling this method. + * Unsafely notifies the loaded {@link DomainObject} that the specified consumer is no longer + * using it. Temporarily exists to provide backwards compatibility. * * @param consumer the consumer - * @throws IOException If there was an issue deleting the saved {@link DomainFile} - * @see #save(Project, MessageLog, TaskMonitor) + * @deprecated Use {@link #close()} instead */ - void deleteSavedDomainFile(Object consumer) throws IOException { - if (domainFile != null && domainFile.exists()) { - domainFile.delete(); - domainFile = null; + @Deprecated(since = "11.5", forRemoval = true) + public void release(Object consumer) { + if (!domainObject.isClosed() && domainObject.isUsedBy(consumer)) { + domainObject.release(consumer); + } + } + + /** + * Closes this {@link Loaded} {@link DomainObject} and releases the reference on the object + * consuming it. + *

+ * NOTE: Any {@link DomainObject}s obtained via {@link #getDomainObject(Object)} must still be + * explicitly {@link DomainObject#release(Object) released} after calling this method, since + * they were obtained with their own consumers. + */ + @Override + public void close() { + if (loadedConsumer != null && !domainObject.isClosed() && + domainObject.isUsedBy(loadedConsumer)) { + domainObject.release(loadedConsumer); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadedOpen.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadedOpen.java new file mode 100644 index 0000000000..de8d63ab4d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/LoadedOpen.java @@ -0,0 +1,56 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.opinion; + +import ghidra.framework.model.DomainFile; +import ghidra.framework.model.DomainObject; +import ghidra.util.task.TaskMonitor; + +/** + * A loaded, open {@link DomainObject} that has already been saved to a {@link DomainFile} + * + * @param The type of open {@link DomainObject} + */ +public class LoadedOpen extends Loaded { + + /** + * Creates a {@link Loaded} view on an existing {@link DomainFile}. This type of {@link Loaded} + * object cannot be re-saved. + * + * @param domainObject The loaded {@link DomainObject} + * @param domainFile The {@link DomainFile} associated with the loaded {@link DomainObject} + * @param consumer A reference to the object "consuming" the returned {@link Loaded} + * {@link DomainObject}, used to ensure the underlying {@link DomainObject} is only closed + * when every consumer is done with it (see {@link #close()}). NOTE: Wrapping a + * {@link DomainObject} in a {@link Loaded} transfers responsibility of releasing the + * given {@link DomainObject} to this {@link Loaded}'s {@link #close()} method. + * @throws LoadException if the given {@link DomainFile} is not open + */ + public LoadedOpen(T domainObject, DomainFile domainFile, Object consumer) throws LoadException { + super(domainObject, domainFile.getName(), null, domainFile.getParent().getPathname(), + consumer); + this.domainFile = domainFile; + if (!domainFile.isOpen()) { + throw new LoadException(domainFile + " is not open"); + } + } + + @Override + public DomainFile save(TaskMonitor monitor) { + return domainFile; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loader.java index 0851780caa..f762a42b2b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/Loader.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -83,11 +83,10 @@ public interface Loader extends ExtensionPoint, Comparable { *

* Note that when the load completes, the returned {@link Loaded} {@link DomainObject}s are not * saved to a project. That is the responsibility of the caller (see - * {@link LoadResults#save(Project, Object, MessageLog, TaskMonitor)}). + * {@link LoadResults#save(boolean, TaskMonitor)}). *

- * It is also the responsibility of the caller to release the returned {@link Loaded} - * {@link DomainObject}s with {@link LoadResults#release(Object)} when they are no longer - * needed. + * It is also the responsibility of the caller to close the returned {@link Loaded} + * {@link DomainObject}s with {@link LoadResults#close()} when they are no longer needed. * * @param provider The bytes to load. * @param loadedName A suggested name for the primary {@link Loaded} {@link DomainObject}. @@ -105,7 +104,9 @@ public interface Loader extends ExtensionPoint, Comparable { * @param loadSpec The {@link LoadSpec} to use during load. * @param options The load options. * @param messageLog The message log. - * @param consumer A consumer object for generated {@link DomainObject}s. + * @param consumer A reference to the object "consuming" the returned {@link LoadResults}, used + * to ensure the underlying {@link Program}s are only closed when every consumer is done + * with it (see {@link LoadResults#close()}). * @param monitor A task monitor. * @return The {@link LoadResults} which contains one or more {@link Loaded} * {@link DomainObject}s (created but not saved). diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java index 75a7e99987..bea22f4463 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoLoader.java @@ -403,14 +403,20 @@ public class MachoLoader extends AbstractLibrarySupportLoader { List searchFolders = getLibrarySearchFolders(loadedPrograms, project, options, log); - List searchPaths = getLibrarySearchPaths( - loadedPrograms.getFirst().getDomainObject(), loadSpec, options, log, monitor); + Program firstProgram = loadedPrograms.getFirst().getDomainObject(this); + List searchPaths; + try { + searchPaths = getLibrarySearchPaths(firstProgram, loadSpec, options, log, monitor); + } + finally { + firstProgram.release(this); + } monitor.initialize(loadedPrograms.size()); for (Loaded loadedProgram : loadedPrograms) { monitor.increment(); - Program program = loadedProgram.getDomainObject(); + Program program = loadedProgram.getDomainObject(this); int id = program.startTransaction("Reexporting"); try { reexport(program, loadedPrograms, searchFolders, searchPaths, options, monitor, @@ -421,6 +427,7 @@ public class MachoLoader extends AbstractLibrarySupportLoader { } finally { program.endTransaction(id, true); + program.release(this); } } } @@ -451,12 +458,11 @@ public class MachoLoader extends AbstractLibrarySupportLoader { for (String path : getReexportPaths(program, log)) { monitor.checkCancelled(); - Program programToRelease = null; + Program lib = null; try { Loaded match = findLibraryInLoadedList(loadedPrograms, path); - Program lib = null; if (match != null) { - lib = match.getDomainObject(); + lib = match.getDomainObject(this); } if (lib == null) { for (DomainFolder searchFolder : searchFolders) { @@ -466,7 +472,9 @@ public class MachoLoader extends AbstractLibrarySupportLoader { DomainObject obj = df.getDomainObject(this, true, true, monitor); if (obj instanceof Program p) { lib = p; - programToRelease = p; + } + else { + obj.release(this); } break; } @@ -498,8 +506,8 @@ public class MachoLoader extends AbstractLibrarySupportLoader { } } finally { - if (programToRelease != null) { - programToRelease.release(this); + if (lib != null) { + lib.release(this); } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MotorolaHexLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MotorolaHexLoader.java index 405b59ddfc..7f77cbdc2a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MotorolaHexLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MotorolaHexLoader.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -172,7 +172,7 @@ public class MotorolaHexLoader extends AbstractProgramLoader { Program prog = createProgram(provider, programName, null, getName(), importerLanguage, importerCompilerSpec, consumer); List> loadedList = - List.of(new Loaded<>(prog, programName, programFolderPath)); + List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer)); boolean success = false; try { loadInto(provider, loadSpec, options, log, prog, monitor); @@ -182,7 +182,7 @@ public class MotorolaHexLoader extends AbstractProgramLoader { } finally { if (!success) { - release(loadedList, consumer); + loadedList.forEach(Loaded::close); } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MzLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MzLoader.java index 02f72176f4..5ef76d9c61 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MzLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MzLoader.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -122,7 +122,7 @@ public class MzLoader extends AbstractLibrarySupportLoader { @Override public int getTierPriority() { - return 60; // we are less priority than PE! Important for AutoImporter + return 60; // we are less priority than PE! Important for ProgramLoader } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/XmlLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/XmlLoader.java index 0294978ade..dce94b88e5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/XmlLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/XmlLoader.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -200,7 +200,7 @@ public class XmlLoader extends AbstractProgramLoader { Program prog = createProgram(provider, programName, imageBase, getName(), importerLanguage, importerCompilerSpec, consumer); List> loadedList = - List.of(new Loaded<>(prog, programName, programFolderPath)); + List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer)); boolean success = false; try { success = doImport(result.lastXmlMgr, options, log, prog, monitor, false); @@ -212,7 +212,7 @@ public class XmlLoader extends AbstractProgramLoader { } finally { if (!success) { - release(loadedList, consumer); + loadedList.forEach(Loaded::close); } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/base/project/GhidraProject.java b/Ghidra/Features/Base/src/main/java/ghidra/base/project/GhidraProject.java index f0df10343f..af012c8ed5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/base/project/GhidraProject.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/base/project/GhidraProject.java @@ -22,7 +22,7 @@ import java.util.HashMap; import java.util.Iterator; import ghidra.app.plugin.core.analysis.AutoAnalysisManager; -import ghidra.app.util.importer.*; +import ghidra.app.util.importer.ProgramLoader; import ghidra.app.util.opinion.*; import ghidra.framework.Application; import ghidra.framework.client.*; @@ -573,7 +573,7 @@ public class GhidraProject { * @param program * the program on which the command is to be applied. */ - public void execute(Command cmd, Program program) { + public void execute(Command cmd, Program program) { AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(program); cmd.applyTo(program); mgr.initializeOptions(); @@ -593,67 +593,196 @@ public class GhidraProject { openPrograms.put(program, id); } - public Program importProgram(File file, Language language, - CompilerSpec compilerSpec) throws CancelledException, DuplicateNameException, - InvalidNameException, VersionException, IOException { - MessageLog messageLog = new MessageLog(); - LoadResults loadResults = AutoImporter.importByLookingForLcs(file, project, null, - language, compilerSpec, this, messageLog, MONITOR); - Program program = loadResults.getPrimaryDomainObject(); - loadResults.releaseNonPrimary(this); - initializeProgram(program, false); - return program; + /** + * Automatically imports the given {@link File} with the best matching {@link Loader} that + * supports the given language and compiler specification. + *

+ * NOTE: It is the responsibility of the caller to release the returned {@link Program} + * with {@link Program#release(Object)} when it is no longer needed, with {@code this} + * {@link GhidraProject} instance as the consumer. + * + * @param file The {@link File} to import + * @param language The desired {@link Language} + * @param compilerSpec The desired {@link CompilerSpec compiler specification} + * @return The imported {@link Program} + * @throws IOException if there was an IO-related problem loading + * @throws LanguageNotFoundException if there was a problem getting the language + * @throws CancelledException if the operation was cancelled + * @throws VersionException if there was an issue with database versions, probably due to a + * failed language upgrade + * @throws LoadException if there was a problem loading + * @deprecated Use {@link ProgramLoader} + */ + @Deprecated(since = "11.5", forRemoval = true) + public Program importProgram(File file, Language language, CompilerSpec compilerSpec) + throws CancelledException, VersionException, LanguageNotFoundException, LoadException, + IOException { + try (LoadResults loadResults = ProgramLoader.builder() + .source(file) + .project(project) + .language(language) + .compiler(compilerSpec) + .monitor(MONITOR) + .load()) { + Program program = loadResults.getPrimaryDomainObject(this); + initializeProgram(program, false); + return program; + } } + /** + * Automatically imports the given {@link File} with the best matching {@link Loader} that + * supports the given processor. + *

+ * NOTE: It is the responsibility of the caller to release the returned {@link Program} + * with {@link Program#release(Object)} when it is no longer needed, with {@code this} + * {@link GhidraProject} instance as the consumer. + * + * @param file The {@link File} to import + * @param processor The desired {@link Processor} + * @return The imported {@link Program} + * @throws IOException if there was an IO-related problem loading + * @throws LanguageNotFoundException if there was a problem getting the language + * @throws CancelledException if the operation was cancelled + * @throws VersionException if there was an issue with database versions, probably due to a + * failed language upgrade + * @throws LoadException if there was a problem loading + * @deprecated Use {@link ProgramLoader} + */ + @Deprecated(since = "11.5", forRemoval = true) public Program importProgram(File file, Processor processor) throws CancelledException, - DuplicateNameException, InvalidNameException, VersionException, IOException { + VersionException, LanguageNotFoundException, LoadException, IOException { LanguageService svc = DefaultLanguageService.getLanguageService(); Language language = svc.getDefaultLanguage(processor); CompilerSpec compilerSpec = language.getDefaultCompilerSpec(); return importProgram(file, language, compilerSpec); } + /** + * Automatically imports the given {@link File} with the given {@link Loader}. + *

+ * NOTE: It is the responsibility of the caller to release the returned {@link Program} + * with {@link Program#release(Object)} when it is no longer needed, with {@code this} + * {@link GhidraProject} instance as the consumer. + * + * @param file The {@link File} to import + * @param loaderClass The desired {@link Loader} + * @return The imported {@link Program} + * @throws IOException if there was an IO-related problem loading + * @throws LanguageNotFoundException if there was a problem getting the language + * @throws CancelledException if the operation was cancelled + * @throws VersionException if there was an issue with database versions, probably due to a + * failed language upgrade + * @throws LoadException if there was a problem loading + * @deprecated Use {@link ProgramLoader} + */ + @Deprecated(since = "11.5", forRemoval = true) public Program importProgram(File file, Class loaderClass) - throws CancelledException, DuplicateNameException, InvalidNameException, - VersionException, IOException { - MessageLog messageLog = new MessageLog(); - LoadResults loadResults = AutoImporter.importByUsingSpecificLoaderClass(file, - project, null, loaderClass, null, this, messageLog, MONITOR); - Program program = loadResults.getPrimaryDomainObject(); - loadResults.releaseNonPrimary(this); - initializeProgram(program, false); - return program; + throws CancelledException, VersionException, LanguageNotFoundException, LoadException, + IOException { + try (LoadResults loadResults = ProgramLoader.builder() + .source(file) + .project(project) + .loaders(loaderClass) + .monitor(MONITOR) + .load()) { + Program program = loadResults.getPrimaryDomainObject(this); + initializeProgram(program, false); + return program; + } } + /** + * Automatically imports the given {@link File} with the given {@link Loader}, {@link Language}, + * and {@link CompilerSpec compiler specification}. + *

+ * NOTE: It is the responsibility of the caller to release the returned {@link Program} + * with {@link Program#release(Object)} when it is no longer needed, with {@code this} + * {@link GhidraProject} instance as the consumer. + * + * @param file The {@link File} to import + * @param loaderClass The desired {@link Loader} + * @param language The desired {@link Language} + * @param compilerSpec The desired {@link CompilerSpec compiler specification} + * @return The imported {@link Program} + * @throws IOException if there was an IO-related problem loading + * @throws LanguageNotFoundException if there was a problem getting the language + * @throws CancelledException if the operation was cancelled + * @throws VersionException if there was an issue with database versions, probably due to a + * failed language upgrade + * @throws LoadException if there was a problem loading + * @deprecated Use {@link ProgramLoader} + */ + @Deprecated(since = "11.5", forRemoval = true) public Program importProgram(File file, Class loaderClass, Language language, - CompilerSpec compilerSpec) throws CancelledException, DuplicateNameException, - InvalidNameException, VersionException, IOException { - MessageLog messageLog = new MessageLog(); - SingleLoaderFilter loaderFilter = new SingleLoaderFilter(loaderClass, null); - LcsHintLoadSpecChooser opinionChoose = new LcsHintLoadSpecChooser(language, compilerSpec); - LoadResults loadResults = - AutoImporter.importFresh(file, project, null, this, messageLog, MONITOR, loaderFilter, - opinionChoose, null, new LoaderArgsOptionChooser(loaderFilter)); - loadResults.releaseNonPrimary(this); - return loadResults.getPrimaryDomainObject(); + CompilerSpec compilerSpec) throws CancelledException, VersionException, + LanguageNotFoundException, LoadException, IOException { + try (LoadResults loadResults = ProgramLoader.builder() + .source(file) + .project(project) + .loaders(loaderClass) + .language(language) + .compiler(compilerSpec) + .monitor(MONITOR) + .load()) { + Program program = loadResults.getPrimaryDomainObject(this); + initializeProgram(program, false); + return program; + } } - public Program importProgram(File file) throws CancelledException, - DuplicateNameException, InvalidNameException, VersionException, IOException { - MessageLog messageLog = new MessageLog(); - LoadResults loadResults = AutoImporter.importByUsingBestGuess(file, project, - null, this, messageLog, MONITOR); - Program program = loadResults.getPrimaryDomainObject(); - loadResults.releaseNonPrimary(this); - initializeProgram(program, false); - return program; + /** + * Automatically imports the given {@link File}. + *

+ * NOTE: It is the responsibility of the caller to release the returned {@link Program} + * with {@link Program#release(Object)} when it is no longer needed, with {@code this} + * {@link GhidraProject} instance as the consumer. + * + * @param file The {@link File} to import + * @return The imported {@link Program} + * @throws IOException if there was an IO-related problem loading + * @throws LanguageNotFoundException if there was a problem getting the language + * @throws CancelledException if the operation was cancelled + * @throws VersionException if there was an issue with database versions, probably due to a + * failed language upgrade + * @throws LoadException if there was a problem loading + * @deprecated Use {@link ProgramLoader} + */ + @Deprecated(since = "11.5", forRemoval = true) + public Program importProgram(File file) throws CancelledException, VersionException, + LanguageNotFoundException, LoadException, IOException { + try (LoadResults loadResults = ProgramLoader.builder() + .source(file) + .project(project) + .monitor(MONITOR) + .load()) { + Program program = loadResults.getPrimaryDomainObject(this); + initializeProgram(program, false); + return program; + } } - public Program importProgramFast(File file) throws CancelledException, DuplicateNameException, - InvalidNameException, VersionException, IOException { - Program program = importByStealingCodeFromAutoImporterByUsingBestGuess(file); - initializeProgram(program, false); - return program; + /** + * Automatically imports the given {@link File}. + *

+ * NOTE: It is the responsibility of the caller to release the returned {@link Program} + * with {@link Program#release(Object)} when it is no longer needed, with {@code this} + * {@link GhidraProject} instance as the consumer. + * + * @param file The {@link File} to import + * @return The imported {@link Program} + * @throws IOException if there was an IO-related problem loading + * @throws LanguageNotFoundException if there was a problem getting the language + * @throws CancelledException if the operation was cancelled + * @throws VersionException if there was an issue with database versions, probably due to a + * failed language upgrade + * @throws LoadException if there was a problem loading + * @deprecated Use {@link ProgramLoader} + */ + @Deprecated(since = "11.5", forRemoval = true) + public Program importProgramFast(File file) throws CancelledException, VersionException, + LanguageNotFoundException, LoadException, IOException { + return importProgram(file); } //================================================================================================== @@ -673,21 +802,6 @@ public class GhidraProject { } } - private Program importByStealingCodeFromAutoImporterByUsingBestGuess(File file) - throws CancelledException, DuplicateNameException, InvalidNameException, - VersionException, IOException { - - MessageLog messageLog = new MessageLog(); - - String programNameOverride = null; - LoadResults loadResults = - AutoImporter.importFresh(file, project, null, this, messageLog, MONITOR, - LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, - programNameOverride, OptionChooser.DEFAULT_OPTIONS); - loadResults.releaseNonPrimary(this); - return loadResults.getPrimaryDomainObject(); - } - //================================================================================================== // Inner Classes //================================================================================================== diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java index 024c27f5f7..f51f3bddb6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterPlugin.java @@ -525,7 +525,7 @@ public class ImporterPlugin extends Plugin Program program = manager.getCurrentProgram(); TaskLauncher.launchModal("Show Load Libraries Dialog", monitor -> { - ImporterUtilities.showLoadLibrariesDialog(program, tool, manager, monitor); + ImporterUtilities.showLoadLibrariesDialog(program, tool, monitor); }); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java index 724f62ef29..70bdea68d7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/ImporterUtilities.java @@ -265,8 +265,16 @@ public class ImporterUtilities { } - public static void showLoadLibrariesDialog(Program program, PluginTool tool, - ProgramManager manager, TaskMonitor monitor) { + /** + * Constructs a {@link LoadLibrariesOptionsDialog} and shows it in the swing thread. If the + * given {@link Program} does not support library loading, a warning message will be shown + * instead. + * + * @param program The {@link Program} + * @param tool The {@link PluginTool} + * @param monitor {@link TaskMonitor} + */ + public static void showLoadLibrariesDialog(Program program, PluginTool tool,TaskMonitor monitor) { Objects.requireNonNull(monitor); @@ -279,9 +287,25 @@ public class ImporterUtilities { try { ByteProvider provider = getProvider(program); + if (provider == null) { + Msg.showWarn(null, null, LoadLibrariesOptionsDialog.TITLE, + "Cannot Load Libraries. Program does not have file bytes associated with it."); + return; + } LoadSpec loadSpec = getLoadSpec(provider, program); + if (loadSpec == null || loadSpec.getLoader() + .getDefaultOptions(provider, loadSpec, null, false) + .stream() + .noneMatch(e -> e.getName() + .equals( + AbstractLibrarySupportLoader.LOAD_ONLY_LIBRARIES_OPTION_NAME))) { + Msg.showWarn(null, null, LoadLibrariesOptionsDialog.TITLE, + "Cannot Load Libraries. Program does not support library loading."); + return; + } AddressFactory addressFactory = loadSpec.getLanguageCompilerSpec().getLanguage().getAddressFactory(); + SystemUtilities.runSwingLater(() -> { OptionsDialog dialog = new LoadLibrariesOptionsDialog(provider, program, tool, loadSpec, () -> addressFactory); @@ -345,20 +369,12 @@ public class ImporterUtilities { Program program = doFSImportHelper((GFileSystemProgramProvider) refdFile.fsRef.getFilesystem(), gfile, destFolder, consumer, monitor); - if (program != null) { - LoadResults loadResults = new LoadResults<>(program, - program.getName(), destFolder.getPathname()); - boolean success = false; - try { - doPostImportProcessing(tool, programManager, fsrl, loadResults, consumer, - "", monitor); - success = true; - } - finally { - if (!success) { - program.release(consumer); - } - } + if (program == null) { + return; + } + try (LoadResults loadResults = new LoadResults<>(program, + program.getName(), tool.getProject(), destFolder.getPathname(), consumer)) { + doPostImportProcessing(tool, programManager, fsrl, loadResults, "", monitor); } } catch (Exception e) { @@ -420,14 +436,14 @@ public class ImporterUtilities { Object consumer = new Object(); MessageLog messageLog = new MessageLog(); - LoadResults loadResults = loadSpec.getLoader() + try (LoadResults loadResults = loadSpec.getLoader() .load(bp, programName, tool.getProject(), destFolder.getPathname(), loadSpec, - options, messageLog, consumer, monitor); + options, messageLog, consumer, monitor)) { + loadResults.save(monitor); + doPostImportProcessing(tool, programManager, fsrl, loadResults, + messageLog.toString(), monitor); + } - loadResults.save(tool.getProject(), consumer, messageLog, monitor); - - doPostImportProcessing(tool, programManager, fsrl, loadResults, consumer, - messageLog.toString(), monitor); } catch (CancelledException e) { // no need to show a message @@ -440,35 +456,39 @@ public class ImporterUtilities { private static Set doPostImportProcessing(PluginTool pluginTool, ProgramManager programManager, FSRL fsrl, - LoadResults loadResults, Object consumer, String importMessages, + LoadResults loadResults, String importMessages, TaskMonitor monitor) throws CancelledException { boolean firstProgram = true; Set importedFilesSet = new HashSet<>(); for (Loaded loaded : loadResults) { monitor.checkCancelled(); - - if (loaded.getDomainObject() instanceof Program program) { - if (programManager != null) { - int openState = firstProgram - ? ProgramManager.OPEN_CURRENT - : ProgramManager.OPEN_VISIBLE; - programManager.openProgram(program, openState); + Object consumer = new Object(); + DomainObject obj = loaded.getDomainObject(consumer); + try { + if (obj instanceof Program) { + if (programManager != null) { + int openState = firstProgram + ? ProgramManager.OPEN_CURRENT + : ProgramManager.OPEN_VISIBLE; + programManager.openProgram((Program) obj, openState); + } + importedFilesSet.add(obj.getDomainFile()); } - importedFilesSet.add(program.getDomainFile()); - } - if (firstProgram) { - // currently we only show results for the imported program, not any libraries - displayResults(pluginTool, loaded.getDomainObject(), - loaded.getDomainObject().getDomainFile(), importMessages); + if (firstProgram) { + // currently we only show results for the imported program, not any libraries + displayResults(pluginTool, obj, obj.getDomainFile(), importMessages); - // Optionally echo loader message log to application.log - if (!Loader.loggingDisabled && !importMessages.isEmpty()) { - Msg.info(ImporterUtilities.class, "Additional info:\n" + importMessages); + // Optionally echo loader message log to application.log + if (!Loader.loggingDisabled && !importMessages.isEmpty()) { + Msg.info(ImporterUtilities.class, "Additional info:\n" + importMessages); + } } + firstProgram = false; + } + finally { + obj.release(consumer); } - loaded.release(consumer); - firstProgram = false; } selectFiles(importedFilesSet); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/LoadLibrariesOptionsDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/LoadLibrariesOptionsDialog.java index 6a5d43fe53..9a83715afd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/LoadLibrariesOptionsDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugin/importer/LoadLibrariesOptionsDialog.java @@ -69,14 +69,14 @@ public class LoadLibrariesOptionsDialog extends OptionsDialog { protected void okCallback() { TaskLauncher.launchNonModal(TITLE, monitor -> { super.okCallback(); - try { - Object consumer = new Object(); - MessageLog messageLog = new MessageLog(); - LoadResults loadResults = loadSpec.getLoader() + Object consumer = new Object(); + MessageLog messageLog = new MessageLog(); + try (LoadResults loadResults = loadSpec.getLoader() .load(provider, program.getDomainFile().getName(), tool.getProject(), program.getDomainFile().getParent().getPathname(), loadSpec, - getOptions(), messageLog, consumer, monitor); - loadResults.save(tool.getProject(), consumer, messageLog, monitor); + getOptions(), messageLog, consumer, monitor)) { + + loadResults.save(monitor); // Display results String importMessages = messageLog.toString(); @@ -90,8 +90,6 @@ public class LoadLibrariesOptionsDialog extends OptionsDialog { else { Msg.showInfo(this, null, TITLE, "The program has no libraries."); } - - loadResults.release(consumer); } catch (CancelledException e) { // no need to show a message diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/tasks/ImportBatchTask.java b/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/tasks/ImportBatchTask.java index 581d579e4b..e48c566fa2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/tasks/ImportBatchTask.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugins/importer/tasks/ImportBatchTask.java @@ -143,26 +143,22 @@ public class ImportBatchTask extends Task { } Pair destInfo = getDestinationInfo(batchLoadConfig, destFolder); - Object consumer = new Object(); try { MessageLog messageLog = new MessageLog(); Project project = AppInfo.getActiveProject(); - LoadResults loadResults = loadSpec.getLoader() + try (LoadResults loadResults = loadSpec.getLoader() .load(byteProvider, fixupProjectFilename(destInfo.second), project, destInfo.first.getPathname(), loadSpec, getOptionsFor(batchLoadConfig, loadSpec, byteProvider), messageLog, - consumer, monitor); + this, monitor)) { - // TODO: accumulate batch results - if (loadResults != null) { - try { - loadResults.save(project, consumer, messageLog, monitor); + // TODO: accumulate batch results + if (loadResults != null) { + loadResults.save(monitor); processImportResults(loadResults, batchLoadConfig, monitor); } - finally { - loadResults.release(consumer); - } } + totalAppsImported++; Msg.info(this, "Imported " + destInfo.first + "/ " + destInfo.second + ", " + @@ -197,14 +193,20 @@ public class ImportBatchTask extends Task { BatchLoadConfig appInfo, TaskMonitor monitor) { for (Loaded loaded : loadResults) { - DomainObject obj = loaded.getDomainObject(); - if (obj instanceof Program program) { - if (programManager != null && totalObjsImported < MAX_PROGRAMS_TO_OPEN) { - programManager.openProgram(program, - totalObjsImported == 0 ? ProgramManager.OPEN_CURRENT - : ProgramManager.OPEN_VISIBLE); + DomainObject obj = loaded.getDomainObject(this); + try { + if (obj instanceof Program program) { + if (programManager != null && totalObjsImported < MAX_PROGRAMS_TO_OPEN) { + programManager.openProgram(program, + totalObjsImported == 0 ? ProgramManager.OPEN_CURRENT + : ProgramManager.OPEN_VISIBLE); + } } } + finally { + obj.release(this); + } + totalObjsImported++; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/examiner/ProgramExaminer.java b/Ghidra/Features/Base/src/main/java/ghidra/program/examiner/ProgramExaminer.java index 4bc9c1696c..5ecc6fe97f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/examiner/ProgramExaminer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/examiner/ProgramExaminer.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -24,8 +24,8 @@ import ghidra.GhidraException; import ghidra.GhidraTestApplicationLayout; import ghidra.app.plugin.core.analysis.EmbeddedMediaAnalyzer; import ghidra.app.util.bin.*; -import ghidra.app.util.importer.AutoImporter; import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.importer.ProgramLoader; import ghidra.app.util.opinion.LoadException; import ghidra.app.util.opinion.LoadResults; import ghidra.framework.*; @@ -81,19 +81,22 @@ public class ProgramExaminer { private ProgramExaminer(ByteProvider provider) throws GhidraException { initializeGhidra(); messageLog = new MessageLog(); - LoadResults loadResults = null; try { - try { - loadResults = AutoImporter.importByUsingBestGuess(provider, null, null, this, - messageLog, TaskMonitor.DUMMY); - program = loadResults.getPrimaryDomainObject(); + try (LoadResults loadResults = ProgramLoader.builder() + .source(provider) + .log(messageLog) + .load()) { + program = loadResults.getPrimaryDomainObject(this); } catch (LoadException e) { try { - program = AutoImporter - .importAsBinary(provider, null, null, defaultLanguage, null, this, - messageLog, TaskMonitor.DUMMY) - .getDomainObject(); + try (LoadResults loadResults = ProgramLoader.builder() + .source(provider) + .language(defaultLanguage) + .log(messageLog) + .load()) { + program = loadResults.getPrimaryDomainObject(this); + } } catch (LoadException e1) { throw new GhidraException( @@ -106,9 +109,6 @@ public class ProgramExaminer { throw new GhidraException(e); } finally { - if (loadResults != null) { - loadResults.releaseNonPrimary(this); - } try { provider.close(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ExternalSymbolResolver.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ExternalSymbolResolver.java index 296326cdea..3e16af4211 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ExternalSymbolResolver.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ExternalSymbolResolver.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,6 +21,7 @@ import java.util.*; import java.util.function.Consumer; import db.Transaction; +import ghidra.app.util.opinion.Loaded; import ghidra.framework.model.*; import ghidra.framework.options.Options; import ghidra.program.model.listing.*; @@ -68,40 +69,33 @@ public class ExternalSymbolResolver implements Closeable { * is called. *

* The program should be fully persisted to the project if using this method, otherwise use - * {@link #addProgramToFixup(String, Program)} and specify the pathname the program will - * be saved to. + * {@link #addProgramToFixup(Loaded)}. * * @param program {@link Program} to fix */ public void addProgramToFixup(Program program) { - addProgramToFixup(program.getDomainFile().getPathname(), program); - } - - /** - * Queues a program into this session that will be fixed when {@link #fixUnresolvedExternalSymbols()} - * is called. - * - * @param programPath string project path to the program - * @param program {@link Program} to fix - */ - public void addProgramToFixup(String programPath, Program program) { + String programPath = program.getDomainFile().getPathname(); programsToFix.add(new ProgramSymbolResolver(program, programPath)); - addLoadedProgram(programPath, program); - } - - /** - * Adds an already opened program to this session, allowing it to be used as an external - * library without needing to look it up in the current project. - * - * @param programPath project path to already opened program - * @param program {@link Program} - */ - public void addLoadedProgram(String programPath, Program program) { if (loadedPrograms.put(programPath, program) == null) { program.addConsumer(this); } } + /** + * Queues a {@link Loaded} {@link Program} into this session that will be fixed when + * {@link #fixUnresolvedExternalSymbols()} is called. + * + * @param loaded The {@link Loaded} {@link Program} to fix + */ + public void addProgramToFixup(Loaded loaded) { + Program program = loaded.getDomainObject(this); + String programPath = loaded.getProjectFolderPath() + loaded.getName(); + programsToFix.add(new ProgramSymbolResolver(program, programPath)); + if (loadedPrograms.put(programPath, program) != null) { + program.release(this); + } + } + /** * Returns true if there was an error encountered when trying to open an external library. * @@ -122,7 +116,7 @@ public class ExternalSymbolResolver implements Closeable { /** * Resolves any unresolved external symbols in each program that has been queued up via - * {@link #addProgramToFixup(String, Program)}. + * {@link #addProgramToFixup(Loaded)} or {@link #addProgramToFixup(Program)}. * * @throws CancelledException if cancelled */ @@ -152,7 +146,7 @@ public class ExternalSymbolResolver implements Closeable { * released during {@link #close()} of this ExternalSymbolServer instance. *

* This cache is shared between all ProgramSymbolResolver instances (that were created - * by calling {@link #addProgramToFixup(String, Program)}). + * by calling {@link #addProgramToFixup(Loaded)} or {@link #addProgramToFixup(Program)}). * * @param libPath project path to a library program * @return {@link Program}, or null if not found or other error during opening diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/app/util/opinion/ApkLoader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/app/util/opinion/ApkLoader.java index 5f821a0ce9..befecf3055 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/app/util/opinion/ApkLoader.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/app/util/opinion/ApkLoader.java @@ -103,13 +103,13 @@ public class ApkLoader extends DexLoader { } finally { if (!success) { - release(allLoadedPrograms, consumer); + allLoadedPrograms.forEach(Loaded::close); } } if (allLoadedPrograms.isEmpty()) { throw new LoadException("Operation finished with no programs to load"); } - link(allLoadedPrograms.stream().map(e -> e.getDomainObject()).toList(), log, monitor); + link(allLoadedPrograms, log, monitor); return allLoadedPrograms; } @@ -300,11 +300,9 @@ public class ApkLoader extends DexLoader { * @param log the message log * @param monitor the task monitor */ - private void link(List programList, MessageLog log, TaskMonitor monitor) { - MultiDexLinker linker = new MultiDexLinker(programList); - try { + private void link(List> programList, MessageLog log, TaskMonitor monitor) { + try (MultiDexLinker linker = new MultiDexLinker(programList)) { linker.link(monitor); - linker.clear(monitor); } catch (Exception e) { log.appendException(e); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/multidex/MultiDexLinker.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/multidex/MultiDexLinker.java index 62572ca170..8ebf282e1a 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/multidex/MultiDexLinker.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/multidex/MultiDexLinker.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,33 +16,20 @@ package ghidra.file.formats.android.multidex; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; +import ghidra.app.util.opinion.Loaded; import ghidra.file.formats.android.dex.DexHeaderFactory; import ghidra.file.formats.android.dex.format.DexHeader; import ghidra.file.formats.android.dex.format.MethodIDItem; import ghidra.file.formats.android.dex.util.DexUtil; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSet; -import ghidra.program.model.listing.Data; -import ghidra.program.model.listing.DataIterator; -import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.*; import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.MemoryBlock; -import ghidra.program.model.symbol.ExternalLocation; -import ghidra.program.model.symbol.ExternalManager; -import ghidra.program.model.symbol.ExternalReference; -import ghidra.program.model.symbol.RefType; -import ghidra.program.model.symbol.Reference; -import ghidra.program.model.symbol.ReferenceManager; -import ghidra.program.model.symbol.SourceType; -import ghidra.util.exception.CancelledException; -import ghidra.util.exception.DuplicateNameException; -import ghidra.util.exception.InvalidInputException; +import ghidra.program.model.symbol.*; +import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; /** @@ -50,7 +37,7 @@ import ghidra.util.task.TaskMonitor; * via the "method_lookup" section using * external references. */ -public final class MultiDexLinker { +public final class MultiDexLinker implements AutoCloseable { private List programs; private Map dexMap = new HashMap<>(); @@ -109,8 +96,11 @@ public final class MultiDexLinker { } } - public MultiDexLinker(List programs) { - this.programs = new ArrayList<>(programs);//create copy of list + public MultiDexLinker(List> loadedPrograms) { + this.programs = new ArrayList<>(); + for (Loaded loaded : loadedPrograms) { + this.programs.add(loaded.getDomainObject(this)); + } } public void link(TaskMonitor monitor) throws CancelledException, IOException, @@ -121,12 +111,12 @@ public final class MultiDexLinker { linkPrograms(monitor); } - public void clear(TaskMonitor monitor) throws CancelledException { - Objects.requireNonNull(monitor); + @Override + public void close() { + programs.forEach(p -> p.release(this)); programs.clear(); dexMap.clear(); for (DexHeader header : cmpMap.keySet()) { - monitor.checkCancelled(); cmpMap.get(header).clear(); } cmpMap.clear(); diff --git a/Ghidra/Features/FunctionID/ghidra_scripts/ImportMSLibs.java b/Ghidra/Features/FunctionID/ghidra_scripts/ImportMSLibs.java index e76d910791..1954efda4e 100644 --- a/Ghidra/Features/FunctionID/ghidra_scripts/ImportMSLibs.java +++ b/Ghidra/Features/FunctionID/ghidra_scripts/ImportMSLibs.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,17 +19,16 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; -import java.util.function.Predicate; import ghidra.app.script.GhidraScript; import ghidra.app.util.bin.*; import ghidra.app.util.bin.format.coff.*; import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader; -import ghidra.app.util.importer.*; +import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.importer.ProgramLoader; import ghidra.app.util.opinion.*; import ghidra.framework.model.DomainFolder; -import ghidra.framework.model.DomainObject; import ghidra.framework.store.local.LocalFileSystem; import ghidra.program.model.lang.LanguageDescription; import ghidra.program.model.listing.Program; @@ -39,8 +38,6 @@ import ghidra.util.task.CancelOnlyWrappingTaskMonitor; import ghidra.util.task.TaskMonitor; public class ImportMSLibs extends GhidraScript { - final static Predicate LOADER_FILTER = new SingleLoaderFilter(MSCoffLoader.class); - final static LoadSpecChooser LOADSPEC_CHOOSER = new CsHintLoadSpecChooser("windows"); @Override protected void run() throws Exception { @@ -103,32 +100,32 @@ public class ImportMSLibs extends GhidraScript { if (CoffMachineType.isMachineTypeDefined(header.getMagic())) { String[] splits = splitPreferredName(preferredName); - LoadResults loadResults = - AutoImporter.importFresh( - coffProvider, - state.getProject(), - root.getPathname(), - this, - log, - new CancelOnlyWrappingTaskMonitor(monitor), - LOADER_FILTER, - LOADSPEC_CHOOSER, - mangleNameBecauseDomainFoldersAreSoRetro(splits[splits.length - 1]), - OptionChooser.DEFAULT_OPTIONS); - - try { - for (Loaded loaded : loadResults) { - if (loaded.getDomainObject() instanceof Program program) { - loaded.save(state.getProject(), log, monitor); + try (LoadResults loadResults = + ProgramLoader.builder() + .source(coffProvider) + .project(state.getProject()) + .projectFolderPath(root.getPathname()) + .loaders(MSCoffLoader.class) + .compiler("windows") + .name( + mangleNameBecauseDomainFoldersAreSoRetro( + splits[splits.length - 1])) + .log(log) + .monitor(new CancelOnlyWrappingTaskMonitor(monitor)) + .load()) { + for (Loaded loaded : loadResults) { + Program program = loaded.getDomainObject(this); + try { + loaded.save(monitor); DomainFolder destination = establishFolder(root, file, program, isDebug, splits); program.getDomainFile().moveTo(destination); } + finally { + program.release(this); + } } } - finally { - loadResults.release(this); - } } } catch (LoadException e) { diff --git a/Ghidra/Features/FunctionID/ghidra_scripts/MSLibBatchImportWorker.java b/Ghidra/Features/FunctionID/ghidra_scripts/MSLibBatchImportWorker.java index bc752138c6..b6e2dd2ba9 100644 --- a/Ghidra/Features/FunctionID/ghidra_scripts/MSLibBatchImportWorker.java +++ b/Ghidra/Features/FunctionID/ghidra_scripts/MSLibBatchImportWorker.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -53,7 +53,6 @@ import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; import java.util.*; -import java.util.function.Predicate; import org.apache.commons.io.FileUtils; @@ -63,19 +62,19 @@ import ghidra.app.util.bin.*; import ghidra.app.util.bin.format.coff.*; import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader; -import ghidra.app.util.importer.*; +import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.importer.ProgramLoader; import ghidra.app.util.opinion.*; import ghidra.framework.model.*; import ghidra.framework.store.local.LocalFileSystem; import ghidra.program.model.listing.Program; import ghidra.util.InvalidNameException; -import ghidra.util.exception.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.VersionException; import ghidra.util.task.TaskMonitor; import utilities.util.FileUtilities; public class MSLibBatchImportWorker extends GhidraScript { - final static Predicate LOADER_FILTER = new SingleLoaderFilter(MSCoffLoader.class); - final static LoadSpecChooser LOADSPEC_CHOOSER = new CsHintLoadSpecChooser("windows"); private static String getProcessId(String fallback) { // something like '@', at least in SUN / Oracle JVMs @@ -173,8 +172,7 @@ public class MSLibBatchImportWorker extends GhidraScript { } private void importLibrary(DomainFolder currentLibraryFolder, File file, MessageLog log) - throws CancelledException, DuplicateNameException, InvalidNameException, - VersionException, IOException { + throws CancelledException, InvalidNameException, VersionException, IOException { try (RandomAccessByteProvider provider = new RandomAccessByteProvider(file)) { if (!CoffArchiveHeader.isMatch(provider)) { return; @@ -196,30 +194,34 @@ public class MSLibBatchImportWorker extends GhidraScript { Pair pair = getFolderAndUniqueFile(currentLibraryFolder, preferredName); - LoadResults loadResults = null; - try { - loadResults = AutoImporter.importFresh(coffProvider, - state.getProject(), pair.first.getPathname(), this, log, - monitor, LOADER_FILTER, LOADSPEC_CHOOSER, pair.second, - OptionChooser.DEFAULT_OPTIONS); + try (LoadResults loadResults = + ProgramLoader.builder() + .source(coffProvider) + .project(state.getProject()) + .projectFolderPath(pair.first.getPathname()) + .loaders(MSCoffLoader.class) + .compiler("windows") + .name(pair.second) + .log(log) + .monitor(monitor) + .load()) { for (Loaded loaded : loadResults) { - if (loaded.getDomainObject() instanceof Program program) { - loaded.save(state.getProject(), log, monitor); - println( - "Imported " + program.getDomainFile().getPathname()); - DomainFile progFile = program.getDomainFile(); - - if (!progFile.isVersioned()) { - progFile.addToVersionControl(initalCheckInComment, - false, monitor); + DomainObject obj = loaded.getDomainObject(this); + try { + if (obj instanceof Program) { + loaded.save(monitor); + DomainFile progFile = obj.getDomainFile(); + println("Imported " + progFile.getPathname()); + if (!progFile.isVersioned()) { + progFile.addToVersionControl(initalCheckInComment, + false, monitor); + } } } - } - } - finally { - if (loadResults != null) { - loadResults.release(this); + finally { + obj.release(this); + } } } } diff --git a/Ghidra/Features/FunctionID/ghidra_scripts/RecursiveRecursiveMSLibImport.java b/Ghidra/Features/FunctionID/ghidra_scripts/RecursiveRecursiveMSLibImport.java index 5d8c89b2d5..e597ddde27 100644 --- a/Ghidra/Features/FunctionID/ghidra_scripts/RecursiveRecursiveMSLibImport.java +++ b/Ghidra/Features/FunctionID/ghidra_scripts/RecursiveRecursiveMSLibImport.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,7 +17,6 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; -import java.util.function.Predicate; import generic.stl.Pair; import ghidra.app.script.GhidraScript; @@ -25,8 +24,10 @@ import ghidra.app.util.bin.*; import ghidra.app.util.bin.format.coff.*; import ghidra.app.util.bin.format.coff.archive.CoffArchiveHeader; import ghidra.app.util.bin.format.coff.archive.CoffArchiveMemberHeader; -import ghidra.app.util.importer.*; -import ghidra.app.util.opinion.*; +import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.importer.ProgramLoader; +import ghidra.app.util.opinion.LoadResults; +import ghidra.app.util.opinion.MSCoffLoader; import ghidra.framework.model.DomainFolder; import ghidra.framework.model.DomainObject; import ghidra.framework.store.local.LocalFileSystem; @@ -35,8 +36,6 @@ import ghidra.util.exception.*; import ghidra.util.task.TaskMonitor; public class RecursiveRecursiveMSLibImport extends GhidraScript { - final static Predicate LOADER_FILTER = new SingleLoaderFilter(MSCoffLoader.class); - final static LoadSpecChooser LOADSPEC_CHOOSER = new CsHintLoadSpecChooser("windows"); @Override protected void run() throws Exception { @@ -152,18 +151,19 @@ public class RecursiveRecursiveMSLibImport extends GhidraScript { Pair pair = establishProgramFolder(currentLibrary, preferredName); - LoadResults loadResults = - AutoImporter.importFresh(coffProvider, state.getProject(), - pair.first.getPathname(), this, log, monitor, LOADER_FILTER, - LOADSPEC_CHOOSER, - mangleNameBecauseDomainFoldersAreSoRetro(pair.second), - OptionChooser.DEFAULT_OPTIONS); - - try { - loadResults.save(state.getProject(), this, log, monitor); - } - finally { - loadResults.release(this); + try (LoadResults loadResults = + ProgramLoader.builder() + .source(coffProvider) + .project(state.getProject()) + .projectFolderPath(pair.first.getPathname()) + .loaders(MSCoffLoader.class) + .compiler("windows") + .name( + mangleNameBecauseDomainFoldersAreSoRetro(pair.second)) + .log(log) + .monitor(monitor) + .load()) { + loadResults.save(monitor); } } } diff --git a/Ghidra/Features/Sarif/src/main/java/sarif/SarifLoader.java b/Ghidra/Features/Sarif/src/main/java/sarif/SarifLoader.java index 00d0c0bf6f..05620ef7ab 100644 --- a/Ghidra/Features/Sarif/src/main/java/sarif/SarifLoader.java +++ b/Ghidra/Features/Sarif/src/main/java/sarif/SarifLoader.java @@ -194,7 +194,7 @@ public class SarifLoader extends AbstractProgramLoader { Program prog = createProgram(provider, programName, imageBase, getName(), importerLanguage, importerCompilerSpec, consumer); List> loadedList = - List.of(new Loaded<>(prog, programName, programFolderPath)); + List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer)); boolean success = false; try { success = doImport(result.lastSarifMgr, options, log, prog, monitor, false); @@ -206,7 +206,7 @@ public class SarifLoader extends AbstractProgramLoader { } finally { if (!success) { - release(loadedList, consumer); + loadedList.forEach(Loaded::close); } } } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerMaintenance.java b/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerMaintenance.java index ffc3d07564..24f77b1aea 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerMaintenance.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerMaintenance.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,8 +20,7 @@ import java.io.File; import org.junit.*; import db.Transaction; -import ghidra.app.util.importer.AutoImporter; -import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.importer.ProgramLoader; import ghidra.app.util.opinion.LoadResults; import ghidra.base.project.GhidraProject; import ghidra.framework.Application; @@ -57,15 +56,19 @@ public class TutorialDebuggerMaintenance extends AbstractGhidraHeadedIntegration @Test public void testRecreateTermminesGzf() throws Throwable { File termmines = Application.getModuleDataFile("TestResources", "termmines").getFile(false); - LoadResults results = AutoImporter.importByUsingBestGuess(termmines, - env.getProject(), "/", this, new MessageLog(), CONSOLE); - program = results.getPrimaryDomainObject(); - try (Transaction tx = program.openTransaction("Analyze")) { - program.setExecutablePath("/tmp/termmines"); - GhidraProject.analyze(program); + try (LoadResults loadResults = ProgramLoader.builder() + .source(termmines) + .project(env.getProject()) + .monitor(CONSOLE) + .load()) { + program = loadResults.getPrimaryDomainObject(this); + try (Transaction tx = program.openTransaction("Analyze")) { + program.setExecutablePath("/tmp/termmines"); + GhidraProject.analyze(program); + } + File dest = new File(termmines.getParentFile(), "termmines.gzf"); + dest.delete(); + program.saveToPackedFile(dest, CONSOLE); } - File dest = new File(termmines.getParentFile(), "termmines.gzf"); - dest.delete(); - program.saveToPackedFile(dest, CONSOLE); } } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerScreenShots.java b/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerScreenShots.java index 001779ebc2..d98174b5fa 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerScreenShots.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/screen/java/ghidraclass/debugger/screenshot/TutorialDebuggerScreenShots.java @@ -15,7 +15,7 @@ */ package ghidraclass.debugger.screenshot; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.awt.Rectangle; import java.awt.event.MouseEvent; @@ -74,8 +74,7 @@ import ghidra.app.plugin.core.terminal.TerminalProvider; import ghidra.app.script.GhidraState; import ghidra.app.services.*; import ghidra.app.services.DebuggerEmulationService.EmulationResult; -import ghidra.app.util.importer.AutoImporter; -import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.importer.ProgramLoader; import ghidra.app.util.opinion.LoadResults; import ghidra.async.AsyncTestUtils; import ghidra.debug.api.modules.ModuleMapProposal; @@ -389,13 +388,15 @@ public class TutorialDebuggerScreenShots extends GhidraScreenShotGenerator protected Program importModule(TraceModule module) throws Throwable { Program prog = null; - try { - long snap = flatDbg.getCurrentSnap(); - MessageLog log = new MessageLog(); - LoadResults result = AutoImporter.importByUsingBestGuess( - new File(module.getName(snap)), env.getProject(), "/", this, log, monitor); - result.save(env.getProject(), this, log, monitor); - prog = result.getPrimaryDomainObject(); + long snap = flatDbg.getCurrentSnap(); + try (LoadResults result = ProgramLoader.builder() + .source(new File(module.getName(snap))) + .project(env.getProject()) + .monitor(monitor) + .load()) { + + result.save(monitor); + prog = result.getPrimaryDomainObject(this); GhidraProgramUtilities.markProgramNotToAskToAnalyze(prog); programManager.openProgram(prog); } diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginTest.java index a0395303fb..9a1b575c69 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePluginTest.java @@ -15,9 +15,8 @@ */ package ghidra.app.plugin.core.debug.gui.tracermi.launcher; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assume.assumeTrue; +import static org.junit.Assert.*; +import static org.junit.Assume.*; import java.nio.file.Paths; import java.util.*; @@ -31,8 +30,7 @@ import ghidra.app.plugin.core.analysis.AnalysisBackgroundCommand; import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; import ghidra.app.services.TraceRmiLauncherService; -import ghidra.app.util.importer.AutoImporter; -import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.importer.ProgramLoader; import ghidra.app.util.opinion.LoadResults; import ghidra.debug.api.ValStr; import ghidra.debug.api.tracermi.TraceRmiLaunchOffer; @@ -80,9 +78,13 @@ public class TraceRmiLauncherServicePluginTest extends AbstractGhidraHeadedDebug @Test public void testGetClassName() throws Exception { ResourceFile rf = Application.getModuleDataFile("TestResources", "HelloWorld.class"); - LoadResults results = AutoImporter.importByUsingBestGuess(rf.getFile(false), - env.getProject(), "/", this, new MessageLog(), monitor); - program = results.getPrimaryDomainObject(); + try (LoadResults results = ProgramLoader.builder() + .source(rf.getFile(false)) + .project(env.getProject()) + .monitor(monitor) + .load()) { + program = results.getPrimaryDomainObject(this); + } AutoAnalysisManager analyzer = AutoAnalysisManager.getAnalysisManager(program); analyzer.reAnalyzeAll(null); Command cmd = new AnalysisBackgroundCommand(analyzer, false); diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ReferencesPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ReferencesPluginScreenShots.java index 44401db883..fef2932754 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ReferencesPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ReferencesPluginScreenShots.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -30,17 +30,16 @@ import generic.theme.GThemeDefaults.Colors; import generic.theme.GThemeDefaults.Colors.Palette; import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; import ghidra.app.plugin.core.references.*; -import ghidra.app.util.importer.*; +import ghidra.app.util.importer.ProgramLoader; import ghidra.app.util.opinion.LoadResults; -import ghidra.app.util.opinion.LoaderService; import ghidra.framework.main.DataTreeDialog; import ghidra.framework.model.Project; import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryAccessException; -import ghidra.util.InvalidNameException; -import ghidra.util.exception.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.VersionException; import ghidra.util.task.TaskMonitor; public class ReferencesPluginScreenShots extends GhidraScreenShotGenerator { @@ -301,20 +300,14 @@ public class ReferencesPluginScreenShots extends GhidraScreenShotGenerator { } - private void importFile(File file) throws CancelledException, DuplicateNameException, - InvalidNameException, VersionException, IOException { - String programNameOverride = null; + private void importFile(File file) throws CancelledException, VersionException, IOException { Project project = env.getProject(); - LoadResults loadResults = AutoImporter.importFresh(file, project, - project.getProjectData().getRootFolder().getPathname(), this, new MessageLog(), - TaskMonitor.DUMMY, LoaderService.ACCEPT_ALL, LoadSpecChooser.CHOOSE_THE_FIRST_PREFERRED, - programNameOverride, OptionChooser.DEFAULT_OPTIONS); - - try { - loadResults.getPrimary().save(project, new MessageLog(), TaskMonitor.DUMMY); - } - finally { - loadResults.release(this); + try (LoadResults loadResults = ProgramLoader.builder() + .source(file) + .project(project) + .projectFolderPath(project.getProjectData().getRootFolder().getPathname()) + .load()) { + loadResults.getPrimary().save(TaskMonitor.DUMMY); } }