From 0d52943d1f7a521ba394fdfabbca22b3fd04321d Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Tue, 18 Nov 2025 14:15:44 -0500 Subject: [PATCH] GP-6132: Fixed a bug in the the PyGhidra headless analyzer that resulted in the wrong exception being thrown when a script tries to import a module that isn't found --- .../ghidra/app/script/GhidraScriptUtil.java | 26 ++++--------------- .../app/util/headless/GhidraScriptRunner.java | 7 ++--- .../app/util/headless/HeadlessAnalyzer.java | 24 ++++++++--------- .../jython/JythonCodeCompletionTest.java | 9 +++---- .../ghidra/jython/JythonInterpreterTest.java | 9 +++---- .../java/ghidra/jython/JythonPluginTest.java | 9 +++---- .../ghidra/jython/JythonScriptInfoTest.java | 9 +++---- .../java/ghidra/jython/JythonScriptTest.java | 4 +-- .../ghidra/pyghidra/PyGhidraPluginTest.java | 5 ++-- .../ghidra/pyghidra/PythonScriptInfoTest.java | 5 ++-- 10 files changed, 40 insertions(+), 67 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java index cdf3da72b0..68980a66a1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java @@ -63,11 +63,11 @@ public class GhidraScriptUtil { } /** - * set the bundle host and start the framework + * Initialize state of GhidraScriptUtil with user and system paths * - * @param aBundleHost the bundle host + * @param aBundleHost the host to use */ - private static void setBundleHost(BundleHost aBundleHost) { + private static void initialize(BundleHost aBundleHost) { if (bundleHost != null) { throw new RuntimeException("GhidraScriptUtil initialized multiple times!"); } @@ -79,22 +79,6 @@ public class GhidraScriptUtil { catch (OSGiException | IOException e) { Msg.error(GhidraScriptUtil.class, "Failed to initialize BundleHost", e); } - } - - /** - * Initialize state of GhidraScriptUtil with user, system, and optional extra system paths. - * - * @param aBundleHost the host to use - * @param extraSystemPaths additional system paths for this run, can be null - * - */ - public static void initialize(BundleHost aBundleHost, List extraSystemPaths) { - setBundleHost(aBundleHost); - if (extraSystemPaths != null) { - for (String path : extraSystemPaths) { - bundleHost.add(new ResourceFile(path), true, true); - } - } bundleHost.add(getUserScriptDirectory(), true, false); bundleHost.add(getSystemScriptDirectories(), true, true); @@ -103,7 +87,7 @@ public class GhidraScriptUtil { /** * dispose of the bundle host and providers list */ - public static void dispose() { + private static void dispose() { if (bundleHost != null) { bundleHost.stopFramework(); bundleHost = null; @@ -455,7 +439,7 @@ public class GhidraScriptUtil { */ public static BundleHost acquireBundleHostReference() { if (referenceCount.getAndIncrement() == 0) { - initialize(new BundleHost(), null); + initialize(new BundleHost()); } return bundleHost; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java index 868ff9d141..3defbd8195 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java @@ -16,12 +16,10 @@ package ghidra.app.util.headless; import java.io.*; -import java.util.List; import generic.jar.ResourceFile; import ghidra.GhidraApplicationLayout; import ghidra.GhidraLaunchable; -import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.script.*; import ghidra.framework.Application; import ghidra.framework.HeadlessGhidraApplicationConfiguration; @@ -36,7 +34,6 @@ import utility.application.ApplicationLayout; */ public class GhidraScriptRunner implements GhidraLaunchable { - private List scriptPaths; private String propertiesFilePath; @Override @@ -47,13 +44,13 @@ public class GhidraScriptRunner implements GhidraLaunchable { System.exit(0); } String logFile = null; //TODO get from arguments? - GhidraScriptUtil.initialize(new BundleHost(), scriptPaths); + GhidraScriptUtil.acquireBundleHostReference(); try { initialize(layout, logFile, true); runScript(args[0]); } finally { - GhidraScriptUtil.dispose(); + GhidraScriptUtil.releaseBundleHostReference(); } } 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 11ece21d9b..7e337df47e 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 @@ -303,8 +303,8 @@ public class HeadlessAnalyzer { } } - List parsedScriptPaths = parseScriptPaths(options.scriptPaths); - GhidraScriptUtil.initialize(new BundleHost(), parsedScriptPaths); + BundleHost bundleHost = GhidraScriptUtil.acquireBundleHostReference(); + bundleHost.add(parseScriptPaths(options.scriptPaths), true, true); try { showConfiguredScriptPaths(); compileScripts(); @@ -365,7 +365,7 @@ public class HeadlessAnalyzer { throw new IOException(e); // unexpected } finally { - GhidraScriptUtil.dispose(); + GhidraScriptUtil.releaseBundleHostReference(); } } @@ -418,8 +418,8 @@ public class HeadlessAnalyzer { } } - List parsedScriptPaths = parseScriptPaths(options.scriptPaths); - GhidraScriptUtil.initialize(new BundleHost(), parsedScriptPaths); + BundleHost bundleHost = GhidraScriptUtil.acquireBundleHostReference(); + bundleHost.add(parseScriptPaths(options.scriptPaths), true, true); try { showConfiguredScriptPaths(); compileScripts(); @@ -471,7 +471,7 @@ public class HeadlessAnalyzer { } } finally { - GhidraScriptUtil.dispose(); + GhidraScriptUtil.releaseBundleHostReference(); } } @@ -693,20 +693,18 @@ public class HeadlessAnalyzer { } } - private List parseScriptPaths(List scriptPaths) { + private List parseScriptPaths(List scriptPaths) { if (scriptPaths == null) { - return null; + return List.of(); } - List parsedScriptPaths = new ArrayList<>(); + List parsedScriptPaths = new ArrayList<>(); for (String path : scriptPaths) { ResourceFile pathFile = Path.fromPathString(path); - String absPath = pathFile.getAbsolutePath(); if (pathFile.exists()) { - parsedScriptPaths.add(absPath); + parsedScriptPaths.add(pathFile); } else { - - Msg.warn(this, "REPORT: Could not find -scriptPath entry, skipping: " + absPath); + Msg.warn(this, "REPORT: Could not find -scriptPath entry, skipping: " + pathFile); } } return parsedScriptPaths; diff --git a/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonCodeCompletionTest.java b/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonCodeCompletionTest.java index 5cab08353b..090cf8cffa 100644 --- a/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonCodeCompletionTest.java +++ b/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonCodeCompletionTest.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. @@ -29,7 +29,6 @@ import org.junit.rules.TemporaryFolder; import generic.jar.ResourceFile; import ghidra.app.plugin.core.console.CodeCompletion; -import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.script.GhidraScriptUtil; import ghidra.test.AbstractGhidraHeadedIntegrationTest; @@ -72,7 +71,7 @@ public class JythonCodeCompletionTest extends AbstractGhidraHeadedIntegrationTes @Before public void setUp() throws Exception { - GhidraScriptUtil.initialize(new BundleHost(), null); + GhidraScriptUtil.acquireBundleHostReference(); interpreter = GhidraJythonInterpreter.get(); executeJythonProgram(simpleTestProgram); } @@ -80,7 +79,7 @@ public class JythonCodeCompletionTest extends AbstractGhidraHeadedIntegrationTes @After public void tearDown() throws Exception { interpreter.cleanup(); - GhidraScriptUtil.dispose(); + GhidraScriptUtil.releaseBundleHostReference(); } @Test diff --git a/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonInterpreterTest.java b/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonInterpreterTest.java index 385465f073..7771fd67f4 100644 --- a/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonInterpreterTest.java +++ b/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonInterpreterTest.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. @@ -22,7 +22,6 @@ import java.io.ByteArrayOutputStream; import org.junit.*; import generic.jar.ResourceFile; -import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.script.GhidraScriptUtil; import ghidra.test.AbstractGhidraHeadedIntegrationTest; @@ -37,7 +36,7 @@ public class JythonInterpreterTest extends AbstractGhidraHeadedIntegrationTest { @Before public void setUp() throws Exception { out = new ByteArrayOutputStream(); - GhidraScriptUtil.initialize(new BundleHost(), null); + GhidraScriptUtil.acquireBundleHostReference(); interpreter = GhidraJythonInterpreter.get(); interpreter.setOut(out); interpreter.setErr(out); @@ -47,7 +46,7 @@ public class JythonInterpreterTest extends AbstractGhidraHeadedIntegrationTest { public void tearDown() throws Exception { out.reset(); interpreter.cleanup(); - GhidraScriptUtil.dispose(); + GhidraScriptUtil.releaseBundleHostReference(); } /** diff --git a/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonPluginTest.java b/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonPluginTest.java index dc8c72be10..e1574e2bbc 100644 --- a/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonPluginTest.java +++ b/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonPluginTest.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,7 +19,6 @@ import static org.junit.Assert.*; import org.junit.*; -import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.script.GhidraScriptUtil; import ghidra.framework.plugintool.PluginTool; import ghidra.test.AbstractGhidraHeadedIntegrationTest; @@ -38,14 +37,14 @@ public class JythonPluginTest extends AbstractGhidraHeadedIntegrationTest { public void setUp() throws Exception { env = new TestEnv(); tool = env.getTool(); - GhidraScriptUtil.initialize(new BundleHost(), null); + GhidraScriptUtil.acquireBundleHostReference(); tool.addPlugin(JythonPlugin.class.getName()); plugin = env.getPlugin(JythonPlugin.class); } @After public void tearDown() throws Exception { - GhidraScriptUtil.dispose(); + GhidraScriptUtil.releaseBundleHostReference(); env.dispose(); } diff --git a/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonScriptInfoTest.java b/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonScriptInfoTest.java index ec3094d913..4b9cd1f41b 100644 --- a/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonScriptInfoTest.java +++ b/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonScriptInfoTest.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. @@ -26,7 +26,6 @@ import javax.swing.KeyStroke; import org.junit.*; import generic.jar.ResourceFile; -import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.ScriptInfo; import ghidra.test.AbstractGhidraHeadedIntegrationTest; @@ -35,7 +34,7 @@ public class JythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest { @Before public void setUp() throws Exception { - GhidraScriptUtil.initialize(new BundleHost(), null); + GhidraScriptUtil.acquireBundleHostReference(); Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR); if (Files.notExists(userScriptDir)) { Files.createDirectories(userScriptDir); @@ -44,7 +43,7 @@ public class JythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest { @After public void tearDown() throws Exception { - GhidraScriptUtil.dispose(); + GhidraScriptUtil.releaseBundleHostReference(); } @Test diff --git a/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonScriptTest.java b/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonScriptTest.java index d7ad95ffb7..85bdc67e77 100644 --- a/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonScriptTest.java +++ b/Ghidra/Features/Jython/src/test.slow/java/ghidra/jython/JythonScriptTest.java @@ -46,14 +46,14 @@ public class JythonScriptTest extends AbstractGhidraHeadedIntegrationTest { public void setUp() throws Exception { env = new TestEnv(); tool = env.getTool(); - GhidraScriptUtil.initialize(new BundleHost(), null); + GhidraScriptUtil.acquireBundleHostReference(); tool.addPlugin(ConsolePlugin.class.getName()); console = tool.getService(ConsoleService.class); } @After public void tearDown() throws Exception { - GhidraScriptUtil.dispose(); + GhidraScriptUtil.releaseBundleHostReference(); env.dispose(); } diff --git a/Ghidra/Features/PyGhidra/src/test.slow/java/ghidra/pyghidra/PyGhidraPluginTest.java b/Ghidra/Features/PyGhidra/src/test.slow/java/ghidra/pyghidra/PyGhidraPluginTest.java index 1f22612f53..6b389df463 100644 --- a/Ghidra/Features/PyGhidra/src/test.slow/java/ghidra/pyghidra/PyGhidraPluginTest.java +++ b/Ghidra/Features/PyGhidra/src/test.slow/java/ghidra/pyghidra/PyGhidraPluginTest.java @@ -18,7 +18,6 @@ package ghidra.pyghidra; import org.junit.After; import org.junit.Before; -import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.script.GhidraScriptUtil; import ghidra.framework.plugintool.PluginTool; import ghidra.test.AbstractGhidraHeadedIntegrationTest; @@ -35,14 +34,14 @@ public class PyGhidraPluginTest extends AbstractGhidraHeadedIntegrationTest { public void setUp() throws Exception { env = new TestEnv(); PluginTool tool = env.getTool(); - GhidraScriptUtil.initialize(new BundleHost(), null); + GhidraScriptUtil.acquireBundleHostReference(); tool.addPlugin(PyGhidraPlugin.class.getName()); env.getPlugin(PyGhidraPlugin.class); } @After public void tearDown() throws Exception { - GhidraScriptUtil.dispose(); + GhidraScriptUtil.releaseBundleHostReference(); env.dispose(); } } diff --git a/Ghidra/Features/PyGhidra/src/test.slow/java/ghidra/pyghidra/PythonScriptInfoTest.java b/Ghidra/Features/PyGhidra/src/test.slow/java/ghidra/pyghidra/PythonScriptInfoTest.java index 0a8ec00509..4fd79903c9 100644 --- a/Ghidra/Features/PyGhidra/src/test.slow/java/ghidra/pyghidra/PythonScriptInfoTest.java +++ b/Ghidra/Features/PyGhidra/src/test.slow/java/ghidra/pyghidra/PythonScriptInfoTest.java @@ -26,7 +26,6 @@ import javax.swing.KeyStroke; import org.junit.*; import generic.jar.ResourceFile; -import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.ScriptInfo; import ghidra.test.AbstractGhidraHeadedIntegrationTest; @@ -35,7 +34,7 @@ public class PythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest { @Before public void setUp() throws Exception { - GhidraScriptUtil.initialize(new BundleHost(), null); + GhidraScriptUtil.acquireBundleHostReference(); Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR); if (Files.notExists(userScriptDir)) { Files.createDirectories(userScriptDir); @@ -44,7 +43,7 @@ public class PythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest { @After public void tearDown() throws Exception { - GhidraScriptUtil.dispose(); + GhidraScriptUtil.releaseBundleHostReference(); } @Test