diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypeMergeManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypeMergeManager.java index bd286cc9d5..3fdbd5ab11 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypeMergeManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypeMergeManager.java @@ -268,11 +268,11 @@ public class DataTypeMergeManager implements MergeResolver { private void processSourceArchiveChanges() throws CancelledException { conflictOption = OPTION_MY; - for (int i = 0; i < myArchiveChangeList.size(); i++) { + for (Long element : myArchiveChangeList) { currentMonitor.checkCanceled(); currentMonitor.setProgress(++progressIndex); - long id = myArchiveChangeList.get(i).longValue(); + long id = element.longValue(); updateSourceArchive(id); } @@ -318,11 +318,11 @@ public class DataTypeMergeManager implements MergeResolver { } private void processSourceArchiveAdditions() throws CancelledException { - for (int i = 0; i < myArchiveAddedList.size(); i++) { + for (Long element : myArchiveAddedList) { currentMonitor.checkCanceled(); currentMonitor.setProgress(++progressIndex); - long id = myArchiveAddedList.get(i).longValue(); + long id = element.longValue(); UniversalID universalID = new UniversalID(id); SourceArchive mySourceArchive = dtms[MY].getSourceArchive(universalID); @@ -381,11 +381,11 @@ public class DataTypeMergeManager implements MergeResolver { private void processSourceArchiveConflicts() throws CancelledException { - for (int i = 0; i < archiveConflictList.size(); i++) { + for (Long element : archiveConflictList) { currentMonitor.checkCanceled(); currentMonitor.setProgress(++progressIndex); - long sourceArchiveID = archiveConflictList.get(i).longValue(); + long sourceArchiveID = element.longValue(); ++currentConflictIndex; handleSourceArchiveConflict(sourceArchiveID, currentConflictIndex); @@ -412,11 +412,11 @@ public class DataTypeMergeManager implements MergeResolver { * @throws CancelledException if task cancelled */ private void processCategoriesAdded() throws CancelledException { - for (int i = 0; i < myCatAddedList.size(); i++) { + for (Long element : myCatAddedList) { currentMonitor.checkCanceled(); currentMonitor.setProgress(++progressIndex); - long id = myCatAddedList.get(i).longValue(); + long id = element.longValue(); Category myCat = dtms[MY].getCategory(id); CategoryPath myPath = myCat.getCategoryPath(); if (dtms[RESULT].containsCategory(myPath)) { @@ -432,11 +432,11 @@ public class DataTypeMergeManager implements MergeResolver { */ private void processCategoryConflicts() throws CancelledException { - for (int i = 0; i < catConflictList.size(); i++) { + for (Long element : catConflictList) { currentMonitor.checkCanceled(); currentMonitor.setProgress(++progressIndex); - long id = catConflictList.get(i).longValue(); + long id = element.longValue(); ++currentConflictIndex; handleCategoryConflict(id, currentConflictIndex); @@ -460,11 +460,11 @@ public class DataTypeMergeManager implements MergeResolver { } private void processCategoryChanges() throws CancelledException { - for (int i = 0; i < myCatChangeList.size(); i++) { + for (Long element : myCatChangeList) { currentMonitor.checkCanceled(); currentMonitor.setProgress(++progressIndex); - long id = myCatChangeList.get(i).longValue(); + long id = element.longValue(); processCategoryRenamed(id); processCategoryMoved(id); @@ -473,10 +473,10 @@ public class DataTypeMergeManager implements MergeResolver { } private void processCategoriesDeleted() throws CancelledException { - for (int i = 0; i < myCatChangeList.size(); i++) { + for (Long element : myCatChangeList) { currentMonitor.checkCanceled(); - long id = myCatChangeList.get(i).longValue(); + long id = element.longValue(); processCategoryDeleted(id); } } @@ -499,8 +499,7 @@ public class DataTypeMergeManager implements MergeResolver { if (fixUpList.size() > 0) { StringBuffer sb = new StringBuffer(); sb.append("The following data types are unresolved:\n"); - for (int i = 0; i < fixUpList.size(); i++) { - FixUpInfo info = fixUpList.get(i); + for (FixUpInfo info : fixUpList) { DataTypeManager dtm = info.getDataTypeManager(); DataType dt = dtm.getDataType(info.id); DataType compDt = dtm.getDataType(info.compID); @@ -1128,26 +1127,22 @@ public class DataTypeMergeManager implements MergeResolver { fixUpList.add(new FixUpInfo(sourceDtID, sourceComponentID, Integer.MAX_VALUE, resolvedDataTypes)); fixUpIDSet.add(sourceDtID); + + // substitute datatype to preserve component name for subsequent fixup + resultCompDt = Undefined1DataType.dataType; } } try { - if (resultCompDt != null) { - // There is a matching component data type in the result. - try { - // If I have compDt, it should now be from result DTM. - destStruct.setFlexibleArrayComponent(resultCompDt, flexDtc.getFieldName(), - comment); - } - catch (IllegalArgumentException e) { - displayError(destStruct, e); - DataType badDt = Undefined1DataType.dataType; - comment = "Couldn't add " + resultCompDt.getDisplayName() + " here. " + - e.getMessage() + " " + ((comment != null) ? (" " + comment) : ""); - destStruct.setFlexibleArrayComponent(badDt, flexDtc.getFieldName(), comment); - } + // Apply resultCompDt as flex array + try { + destStruct.setFlexibleArrayComponent(resultCompDt, flexDtc.getFieldName(), comment); } - else { - destStruct.clearFlexibleArrayComponent(); + catch (IllegalArgumentException e) { + displayError(destStruct, e); + DataType badDt = Undefined1DataType.dataType; + comment = "Couldn't add " + resultCompDt.getDisplayName() + " here. " + + e.getMessage() + " " + ((comment != null) ? (" " + comment) : ""); + destStruct.setFlexibleArrayComponent(badDt, flexDtc.getFieldName(), comment); } } catch (IllegalArgumentException e) { @@ -1216,7 +1211,9 @@ public class DataTypeMergeManager implements MergeResolver { } else { // must have been deleted in LATEST - // put an entry in the fixup list if this is a conflict + // put an entry in the fixup list if this is a conflict. + // NOTE: This may also be caused by a replaced datatype but + // we have no indication as to what the replacment was deletedInLatest = true; } } @@ -1599,11 +1596,11 @@ public class DataTypeMergeManager implements MergeResolver { */ private void processDataTypeChanges() throws CancelledException { - for (int i = 0; i < myDtChangeList.size(); i++) { + for (Long element : myDtChangeList) { currentMonitor.checkCanceled(); currentMonitor.setProgress(++progressIndex); - long id = myDtChangeList.get(i).longValue(); + long id = element.longValue(); DataType dt = dtms[MY].getDataType(id); if ((dt instanceof Pointer) || (dt instanceof Array) || (dt instanceof BuiltIn)) { @@ -1681,8 +1678,7 @@ public class DataTypeMergeManager implements MergeResolver { } private boolean isParent(CategoryPath catPath) { - for (int i = 0; i < myDtAddedList.size(); i++) { - Long id = myDtAddedList.get(i); + for (Long id : myDtAddedList) { DataType dt = dtms[MY].getDataType(id.longValue()); if (catPath.equals(dt.getCategoryPath())) { return true; @@ -2225,10 +2221,10 @@ public class DataTypeMergeManager implements MergeResolver { } private void processDataTypesDeleted() throws CancelledException { - for (int i = 0; i < myDtChangeList.size(); i++) { + for (Long element : myDtChangeList) { currentMonitor.checkCanceled(); - long id = myDtChangeList.get(i).longValue(); + long id = element.longValue(); processDataTypeDeleted(id); } } @@ -2237,11 +2233,11 @@ public class DataTypeMergeManager implements MergeResolver { * */ private void processDataTypesAdded() throws CancelledException { - for (int i = 0; i < myDtAddedList.size(); i++) { + for (Long element : myDtAddedList) { currentMonitor.checkCanceled(); currentMonitor.setProgress(++progressIndex); - long myDtKey = myDtAddedList.get(i).longValue(); + long myDtKey = element.longValue(); DataType myDt = dtms[MY].getDataType(myDtKey); if (equivalentDataTypeFound(myDtKey, myDt)) { @@ -3360,7 +3356,7 @@ public class DataTypeMergeManager implements MergeResolver { if (nextOrdinal > maxOrdinal) { break; } - struct.getComponent(nextOrdinal); + dtc = struct.getComponent(nextOrdinal); } return null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java index 5e1b57d316..18903f7a4a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java @@ -22,7 +22,6 @@ import java.util.*; import java.util.Map.Entry; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; -import java.util.function.Function; import java.util.stream.Collectors; import org.apache.felix.framework.FrameworkFactory; @@ -63,8 +62,7 @@ public class BundleHost { private static final String SAVE_STATE_TAG_ACTIVE = "BundleHost_ACTIVE"; private static final String SAVE_STATE_TAG_SYSTEM = "BundleHost_SYSTEM"; - Map fileToBundleMap = new HashMap<>(); - Map bundleLocationToBundleMap = new HashMap<>(); + private final BundleMap bundleMap = new BundleMap(); BundleContext frameworkBundleContext; Framework felixFramework; @@ -93,7 +91,7 @@ public class BundleHost { * @return false if the bundle was already enabled */ public boolean enable(ResourceFile bundleFile) { - GhidraBundle bundle = fileToBundleMap.get(bundleFile); + GhidraBundle bundle = bundleMap.get(bundleFile); if (bundle == null) { bundle = add(bundleFile, true, false); return true; @@ -139,7 +137,7 @@ public class BundleHost { * @return a {@link GhidraBundle} or {@code null} */ public GhidraBundle getExistingGhidraBundle(ResourceFile bundleFile) { - GhidraBundle bundle = fileToBundleMap.get(bundleFile); + GhidraBundle bundle = bundleMap.get(bundleFile); if (bundle == null) { Msg.showError(this, null, "ghidra bundle cache", "getExistingGhidraBundle expected a GhidraBundle created at " + bundleFile + @@ -156,7 +154,7 @@ public class BundleHost { * @return a {@link GhidraBundle} or {@code null} */ public GhidraBundle getGhidraBundle(ResourceFile bundleFile) { - return fileToBundleMap.get(bundleFile); + return bundleMap.get(bundleFile); } private static GhidraBundle createGhidraBundle(BundleHost bundleHost, ResourceFile bundleFile, @@ -189,25 +187,11 @@ public class BundleHost { */ public GhidraBundle add(ResourceFile bundleFile, boolean enabled, boolean systemBundle) { GhidraBundle bundle = createGhidraBundle(this, bundleFile, enabled, systemBundle); - fileToBundleMap.put(bundleFile, bundle); - bundleLocationToBundleMap.put(bundle.getLocationIdentifier(), bundle); + bundleMap.add(bundle); fireBundleAdded(bundle); return bundle; } - private Set dedupeBundleFiles(List bundleFiles) { - Set dedupedBundleFiles = new HashSet<>(bundleFiles); - Iterator bundleFileIterator = dedupedBundleFiles.iterator(); - while (bundleFileIterator.hasNext()) { - ResourceFile bundleFile = bundleFileIterator.next(); - if (fileToBundleMap.containsKey(bundleFile)) { - bundleFileIterator.remove(); - Msg.warn(this, "adding an already managed bundle: " + bundleFile.getAbsolutePath()); - } - } - return dedupedBundleFiles; - } - /** * Create new GhidraBundles and add to the list of managed bundles. All GhidraBundles created * with the same {@code enabled} and {@code systemBundle} values. @@ -219,17 +203,8 @@ public class BundleHost { */ public Collection add(List bundleFiles, boolean enabled, boolean systemBundle) { - Set dedupedBundleFiles = dedupeBundleFiles(bundleFiles); - Map newBundleMap = dedupedBundleFiles.stream() - .collect(Collectors.toUnmodifiableMap(Function.identity(), - bundleFile -> createGhidraBundle(BundleHost.this, bundleFile, enabled, - systemBundle))); - fileToBundleMap.putAll(newBundleMap); - bundleLocationToBundleMap.putAll(newBundleMap.values() - .stream() - .collect(Collectors.toUnmodifiableMap(GhidraBundle::getLocationIdentifier, - Function.identity()))); - Collection newBundles = newBundleMap.values(); + Collection newBundles = bundleMap.computeAllIfAbsent(bundleFiles, + bundleFile -> createGhidraBundle(BundleHost.this, bundleFile, enabled, systemBundle)); fireBundlesAdded(newBundles); return newBundles; } @@ -240,10 +215,7 @@ public class BundleHost { * @param bundles the bundles to add */ private void add(List bundles) { - for (GhidraBundle bundle : bundles) { - fileToBundleMap.put(bundle.getFile(), bundle); - bundleLocationToBundleMap.put(bundle.getLocationIdentifier(), bundle); - } + bundleMap.addAll(bundles); fireBundlesAdded(bundles); } @@ -253,8 +225,7 @@ public class BundleHost { * @param bundleFile the file of the bundle to remove */ public void remove(ResourceFile bundleFile) { - GhidraBundle bundle = fileToBundleMap.remove(bundleFile); - bundleLocationToBundleMap.remove(bundle.getLocationIdentifier()); + GhidraBundle bundle = bundleMap.remove(bundleFile); fireBundleRemoved(bundle); } @@ -264,8 +235,7 @@ public class BundleHost { * @param bundleLocation the location id of the bundle to remove */ public void remove(String bundleLocation) { - GhidraBundle bundle = bundleLocationToBundleMap.remove(bundleLocation); - fileToBundleMap.remove(bundle.getFile()); + GhidraBundle bundle = bundleMap.remove(bundleLocation); fireBundleRemoved(bundle); } @@ -275,8 +245,7 @@ public class BundleHost { * @param bundle the bundle to remove */ public void remove(GhidraBundle bundle) { - fileToBundleMap.remove(bundle.getFile()); - bundleLocationToBundleMap.remove(bundle.getLocationIdentifier()); + bundleMap.remove(bundle); fireBundleRemoved(bundle); } @@ -286,10 +255,7 @@ public class BundleHost { * @param bundles the bundles to remove */ public void remove(Collection bundles) { - for (GhidraBundle bundle : bundles) { - fileToBundleMap.remove(bundle.getFile()); - bundleLocationToBundleMap.remove(bundle.getLocationIdentifier()); - } + bundleMap.removeAll(bundles); fireBundlesRemoved(bundles); } @@ -338,7 +304,7 @@ public class BundleHost { * @return all the bundles */ public Collection getGhidraBundles() { - return fileToBundleMap.values(); + return bundleMap.getGhidraBundles(); } /** @@ -347,7 +313,7 @@ public class BundleHost { * @return all the bundle files */ public Collection getBundleFiles() { - return fileToBundleMap.keySet(); + return bundleMap.getBundleFiles(); } void dumpLoadedBundles() { @@ -861,7 +827,7 @@ public class BundleHost { boolean isEnabled = bundleIsEnabled[i]; boolean isActive = bundleIsActive[i]; boolean isSystem = bundleIsSystem[i]; - GhidraBundle bundle = fileToBundleMap.get(bundleFile); + GhidraBundle bundle = bundleMap.get(bundleFile); if (bundle != null) { if (isEnabled != bundle.isEnabled()) { bundle.setEnabled(isEnabled); @@ -900,14 +866,15 @@ public class BundleHost { * @param saveState the state object */ public void saveManagedBundleState(SaveState saveState) { - int numBundles = fileToBundleMap.size(); + Collection bundles = bundleMap.getGhidraBundles(); + int numBundles = bundles.size(); String[] bundleFiles = new String[numBundles]; boolean[] bundleIsEnabled = new boolean[numBundles]; boolean[] bundleIsActive = new boolean[numBundles]; boolean[] bundleIsSystem = new boolean[numBundles]; int index = 0; - for (GhidraBundle bundle : fileToBundleMap.values()) { + for (GhidraBundle bundle : bundles) { bundleFiles[index] = generic.util.Path.toPathString(bundle.getFile()); bundleIsEnabled[index] = bundle.isEnabled(); bundleIsActive[index] = bundle.isActive(); @@ -1081,7 +1048,7 @@ public class BundleHost { GhidraBundle bundle; switch (event.getType()) { case BundleEvent.STARTED: - bundle = bundleLocationToBundleMap.get(osgiBundle.getLocation()); + bundle = bundleMap.getBundleAtLocation(osgiBundle.getLocation()); if (bundle != null) { fireBundleActivationChange(bundle, true); } @@ -1093,7 +1060,7 @@ public class BundleHost { break; // force "inactive" updates for all other states default: - bundle = bundleLocationToBundleMap.get(osgiBundle.getLocation()); + bundle = bundleMap.getBundleAtLocation(osgiBundle.getLocation()); if (bundle != null) { fireBundleActivationChange(bundle, false); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleMap.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleMap.java new file mode 100644 index 0000000000..cee4bf6519 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleMap.java @@ -0,0 +1,225 @@ +/* ### + * 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.plugin.core.osgi; + +import java.util.*; +import java.util.concurrent.locks.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import generic.jar.ResourceFile; + +/** + * A thread-safe container that maps {@link GhidraBundle}s by file and bundle location. + */ +public class BundleMap { + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final Lock readLock = lock.readLock(); + private final Lock writeLock = lock.writeLock(); + + private final Map bundlesByFile = new HashMap<>(); + private final Map bundlesByLocation = new HashMap<>(); + + /** + * Maps associations between a bundle, its file, and its bundle location. + * + * @param bundle a GhidraBundle object + */ + public void add(GhidraBundle bundle) { + writeLock.lock(); + try { + bundlesByFile.put(bundle.getFile(), bundle); + bundlesByLocation.put(bundle.getLocationIdentifier(), bundle); + } + finally { + lock.writeLock().unlock(); + } + } + + /** + * Maps bundles in a collection. + * + *

This is the same as calling {@link BundleMap#add(GhidraBundle)} for each bundle in {@code bundles}. + * + * @param bundles a collection of GhidraBundle objects + */ + public void addAll(Collection bundles) { + writeLock.lock(); + try { + for (GhidraBundle bundle : bundles) { + bundlesByFile.put(bundle.getFile(), bundle); + bundlesByLocation.put(bundle.getLocationIdentifier(), bundle); + } + } + finally { + writeLock.unlock(); + } + } + + /** + * Removes the mappings of a bundle. + * + * @param bundle a GhidraBundle object + */ + public void remove(GhidraBundle bundle) { + writeLock.lock(); + try { + bundlesByFile.remove(bundle.getFile()); + bundlesByLocation.remove(bundle.getLocationIdentifier()); + } + finally { + writeLock.unlock(); + } + } + + /** + * Removes all mappings of each bundle from a collection. + * + * This is the same as calling {@link #remove(GhidraBundle)} for each bundle in {@code bundles}. + * + * @param bundles a collection of GhidraBundle objects + */ + public void removeAll(Collection bundles) { + writeLock.lock(); + try { + for (GhidraBundle bundle : bundles) { + bundlesByFile.remove(bundle.getFile()); + bundlesByLocation.remove(bundle.getLocationIdentifier()); + } + } + finally { + writeLock.unlock(); + } + } + + /** + * Removes the mapping for a bundle with a given bundle location. + * + * @param bundleLocation a bundle location + * @return the bundle removed + */ + public GhidraBundle remove(String bundleLocation) { + writeLock.lock(); + try { + GhidraBundle bundle = bundlesByLocation.remove(bundleLocation); + bundlesByFile.remove(bundle.getFile()); + return bundle; + } + finally { + writeLock.unlock(); + } + } + + /** + * Removes the mapping for a bundle with a given file. + + * @param bundleFile a bundle file + * @return the bundle removed + */ + public GhidraBundle remove(ResourceFile bundleFile) { + writeLock.lock(); + try { + GhidraBundle bundle = bundlesByFile.remove(bundleFile); + bundlesByLocation.remove(bundle.getLocationIdentifier()); + return bundle; + } + finally { + writeLock.unlock(); + } + } + + /** + * Creates and maps bundles from files in a collection that aren't already mapped. + * + * @param bundleFiles a collection of bundle files + * @param ctor a constructor for a GhidraBundle given a bundle file + * @return the newly created GhidraBundle objects + */ + public Collection computeAllIfAbsent(Collection bundleFiles, + Function ctor) { + writeLock.lock(); + try { + Set newBundleFiles = new HashSet<>(bundleFiles); + newBundleFiles.removeAll(bundlesByFile.keySet()); + List newBundles = + newBundleFiles.stream().map(ctor).collect(Collectors.toList()); + addAll(newBundles); + return newBundles; + } + finally { + writeLock.unlock(); + } + } + + /** + * Returns the bundle with the given location. + * + * @param location a bundle location + * @return the bundle found or null + */ + public GhidraBundle getBundleAtLocation(String location) { + readLock.lock(); + try { + return bundlesByLocation.get(location); + } + finally { + readLock.unlock(); + } + } + + /** + * Returns the bundle with the given file. + * + * @param bundleFile a bundle file + * @return the bundle found or null + */ + public GhidraBundle get(ResourceFile bundleFile) { + readLock.lock(); + try { + return bundlesByFile.get(bundleFile); + } + finally { + readLock.unlock(); + } + } + + /** + * @return the currently mapped bundles + */ + public Collection getGhidraBundles() { + readLock.lock(); + try { + return new ArrayList<>(bundlesByFile.values()); + } + finally { + readLock.unlock(); + } + } + + /** + * @return the currently mapped bundle files + */ + public Collection getBundleFiles() { + readLock.lock(); + try { + return new ArrayList<>(bundlesByFile.keySet()); + } + finally { + readLock.unlock(); + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java index a9f18480ab..d94e14cab1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProvider.java @@ -133,4 +133,19 @@ public abstract class GhidraScriptProvider protected void writeBody(PrintWriter writer) { writer.println(getCommentCharacter() + "TODO Add User Code Here"); } + + /** + * Fixup a script name for searching in script directories. + * + *

This method is part of a poorly specified behavior that is due for future amendment, + * see {@link GhidraScriptUtil#fixupName(String)}. + * + * @param scriptName the name of the script, must end with this provider's extension + * @return a (relative) file path to the corresponding script + */ + @Deprecated + protected String fixupName(String scriptName) { + return scriptName; + } + } 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 5f2ac752b6..e386c800d9 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 @@ -28,6 +28,7 @@ import generic.jar.ResourceFile; import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.plugin.core.osgi.OSGiException; import ghidra.app.plugin.core.script.GhidraScriptMgrPlugin; +import ghidra.app.util.headless.HeadlessAnalyzer; import ghidra.framework.Application; import ghidra.util.Msg; import ghidra.util.classfinder.ClassSearcher; @@ -139,8 +140,7 @@ public class GhidraScriptUtil { } catch (IOException e) { Msg.error(GhidraScriptUtil.class, - "Failed to find script in any script directory: " + sourceFile.toString(), - e); + "Failed to find script in any script directory: " + sourceFile.toString(), e); } return null; } @@ -293,14 +293,7 @@ public class GhidraScriptUtil { * @return the Ghidra script provider */ public static GhidraScriptProvider getProvider(ResourceFile scriptFile) { - String scriptFileName = scriptFile.getName().toLowerCase(); - - for (GhidraScriptProvider provider : getProviders()) { - if (scriptFileName.endsWith(provider.getExtension().toLowerCase())) { - return provider; - } - } - return null; + return findProvider(scriptFile.getName()); } /** @@ -310,13 +303,23 @@ public class GhidraScriptUtil { * @return true if a provider exists that can process the specified file */ public static boolean hasScriptProvider(ResourceFile scriptFile) { - String scriptFileName = scriptFile.getName().toLowerCase(); + return findProvider(scriptFile.getName()) != null; + } + + /** + * Find the provider whose extension matches the given filename extension. + * + * @param fileName name of script file + * @return the first matching provider or null if no provider matches + */ + private static GhidraScriptProvider findProvider(String fileName) { + fileName = fileName.toLowerCase(); for (GhidraScriptProvider provider : getProviders()) { - if (scriptFileName.endsWith(provider.getExtension().toLowerCase())) { - return true; + if (fileName.endsWith(provider.getExtension().toLowerCase())) { + return provider; } } - return false; + return null; } /** @@ -362,31 +365,35 @@ public class GhidraScriptUtil { } /** - * Fixup name issues, such as package parts in the name and inner class names. + * Fix script name issues for searching in script directories. + * If no provider can be identified, Java is assumed. * - *

This method can handle names with or without '.java' at the end; names with - * '$' (inner classes) and names with '.' characters for package separators + *

This method is part of a poorly specified behavior that is due for future amendment. * + *

It is used by {@link GhidraScript#runScript(String)} methods, + * {@link #createNewScript(String, String, ResourceFile, List)}, and by {@link HeadlessAnalyzer} for + * {@code preScript} and {@code postScript}. The intent was to allow some freedom in how a user specifies + * a script in two ways: 1) if the extension is omitted ".java" is assumed and 2) if a Java class name is + * given it's converted to a relative path. + * * @param name the name of the script - * @return the name as a '.java' file path (with '/'s and not '.'s) + * @return the name as a file path */ + @Deprecated static String fixupName(String name) { - if (name.endsWith(".java")) { - name = name.substring(0, name.length() - 5); + GhidraScriptProvider provider = findProvider(name); + // assume Java if no provider matched + if (provider == null) { + name = name + ".java"; + provider = findProvider(".java"); } - - String path = name.replace('.', '/'); - int innerClassIndex = path.indexOf('$'); - if (innerClassIndex != -1) { - path = path.substring(0, innerClassIndex); - } - return path + ".java"; + return provider.fixupName(name); } static ResourceFile findScriptFileInPaths(Collection scriptDirectories, - String filename) { + String name) { - String validatedName = fixupName(filename); + String validatedName = fixupName(name); for (ResourceFile resourceFile : scriptDirectories) { if (resourceFile.isDirectory()) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java index d58578db7b..39f99ffd91 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java @@ -164,4 +164,29 @@ public class JavaScriptProvider extends GhidraScriptProvider { return "//"; } + /** + * + * Fix script name for search in script directories, such as Java package parts in the name and inner class names. + * + *

This method can handle names with '$' (inner classes) and names with '.' + * characters for package separators + * + *

It is part of a poorly specified behavior that is due for future amendment, + * see {@link GhidraScriptUtil#fixupName(String)}. + * + * @param scriptName the name of the script + * @return the name as a '.java' file path (with '/'s and not '.'s) + */ + @Override + protected String fixupName(String scriptName) { + scriptName = scriptName.substring(0, scriptName.length() - 5); + + String path = scriptName.replace('.', '/'); + int innerClassIndex = path.indexOf('$'); + if (innerClassIndex != -1) { + path = path.substring(0, innerClassIndex); + } + return path + ".java"; + } + } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge3Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge3Test.java index ab598a2d4a..1a6e1fc287 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge3Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge3Test.java @@ -17,8 +17,7 @@ package ghidra.app.merge.datatypes; import static org.junit.Assert.*; -import org.junit.Assert; -import org.junit.Test; +import org.junit.*; import ghidra.program.database.*; import ghidra.program.model.data.*; @@ -1247,6 +1246,122 @@ public class DataTypeMerge3Test extends AbstractDataTypeMergeTest { } + @Test + @Ignore + public void testEditStructureWithReplacementAndRemoval() throws Exception { + + // See GP-585 for design issue preventing this test from passing + + mtf.initialize("notepad", new OriginalProgramModifierListener() { + + @Override + public void modifyPrivate(ProgramDB program) throws Exception { + boolean commit = false; + DataTypeManager dtm = program.getDataTypeManager(); + int transactionID = program.startTransaction("test"); + try { + Structure s = (Structure) dtm.getDataType("/Category5/Test"); + DataType dt = dtm.getDataType("/MISC/FooTypedef"); + s.setFlexibleArrayComponent(dt, "foo", ""); + commit = true; + } + finally { + program.endTransaction(transactionID, commit); + } + } + + @Override + public void modifyLatest(ProgramDB program) throws Exception { + boolean commit = false; + DataTypeManager dtm = program.getDataTypeManager(); + int transactionID = program.startTransaction("test"); + try { + TypeDef td = (TypeDef) dtm.getDataType("/BF"); + // + // NOTE: Merge does not handle datatype replacements as one might hope + // If latest version has defined data/components based upon a type which has + // been replaced in private, the replaced datatype will be treated as removed + // + dtm.replaceDataType(td, new TypedefDataType("NewBF", IntegerDataType.dataType), + true); + DataType dt = dtm.getDataType("/MISC/FooTypedef"); + dtm.remove(dt, TaskMonitor.DUMMY); + commit = true; + } + finally { + program.endTransaction(transactionID, commit); + } + + DataType dt1 = dtm.getDataType("/BF"); + assertNull(dt1); + } + + @Override + public void modifyOriginal(ProgramDB program) throws Exception { + boolean commit = false; + DataTypeManager dtm = program.getDataTypeManager(); + int transactionID = program.startTransaction("test"); + try { + TypeDef td = new TypedefDataType("BF", IntegerDataType.dataType); + + Structure struct = new StructureDataType(new CategoryPath("/Category5"), "Test", + 0, program.getDataTypeManager()); + struct.add(td); + struct.insertBitFieldAt(3, 2, 6, td, 2, "bf1", null); + struct.insertBitFieldAt(3, 2, 4, td, 2, "bf2", null); + struct.add(new WordDataType()); + struct.add(new QWordDataType()); + + struct.setFlexibleArrayComponent(td, "flex", "my flex"); + + dtm.addDataType(struct, null); + commit = true; + } + catch (Exception e) { + e.printStackTrace(); + Assert.fail(e.toString()); + } + finally { + program.endTransaction(transactionID, commit); + } + } + }); + + executeMerge(DataTypeMergeManager.OPTION_MY); + + DataTypeManager dtm = resultProgram.getDataTypeManager(); + + DataType dt = dtm.getDataType("/Category5/Test"); + assertTrue(dt instanceof Structure); + Structure s = (Structure) dt; + /** Current Result for /Category5/Test + * + Unaligned + Structure Test { + 4 int:2(6) 1 bf1 "" + 4 int:2(4) 1 bf2 "" + 5 word 2 null "" + 7 qword 8 null "" + } + Size = 15 Actual Alignment = 1 + * + * See assertion below for preferred result + */ + //@formatter:off + assertEquals("/Category5/Test\n" + + "Unaligned\n" + + "Structure Test {\n" + + " 0 NewBF 4 null \"\"\n" + + " 4 NewBF:2(6) 1 bf1 \"\"\n" + + " 4 NewBF:2(4) 1 bf2 \"\"\n" + + " 5 word 2 null \"\"\n" + + " 7 qword 8 null \"\"\n" + + " Undefined1[0] 0 foo \"\"\n" + // reflects removal of /MISC/FooTypedef + "}\n" + + "Size = 15 Actual Alignment = 1\n", s.toString()); + //@formatter:on + } + @Test public void testEditUnions() throws Exception { diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java index a6749c1bf9..22800bded6 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java @@ -669,6 +669,8 @@ public class DefaultGraphDisplay implements GraphDisplay { // always get the current predicate from the main view and test with it, satellite.getRenderContext() .setVertexIncludePredicate(v -> viewer.getRenderContext().getVertexIncludePredicate().test(v)); + satellite.getRenderContext() + .setEdgeIncludePredicate(e -> viewer.getRenderContext().getEdgeIncludePredicate().test(e)); satellite.getComponent().setBorder(BorderFactory.createEtchedBorder()); parentViewer.getComponent().addComponentListener(new ComponentAdapter() { @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java index 8cbe939628..5ae5403822 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java @@ -1473,11 +1473,12 @@ abstract public class DataTypeManagerDB implements DataTypeManager { public DataType getDataType(String dataTypePath) { // Category path now has sourceID followed by ":" followed by path under that source. String name = getName(); - if (dataTypePath.startsWith(name)) { - dataTypePath = dataTypePath.substring(name.length()); + int nameLen = name.length(); + if (dataTypePath.length() > nameLen && dataTypePath.charAt(nameLen) == '/' && + dataTypePath.startsWith(name)) { + dataTypePath = dataTypePath.substring(nameLen); } - - if (!dataTypePath.startsWith("/")) { + else if (!dataTypePath.startsWith("/")) { return null; } diff --git a/Ghidra/RuntimeScripts/Windows/support/createPdbXmlFiles.bat b/Ghidra/RuntimeScripts/Windows/support/createPdbXmlFiles.bat index d1742d19f5..44826dd39e 100644 --- a/Ghidra/RuntimeScripts/Windows/support/createPdbXmlFiles.bat +++ b/Ghidra/RuntimeScripts/Windows/support/createPdbXmlFiles.bat @@ -26,7 +26,7 @@ set OS_DIR=build\os :continue REM create absolute path -for /f %%i in ("%GHIDRA_DIR%") do set GHIDRA_DIR=%%~fi +for /f "delims=" %%i in ("%GHIDRA_DIR%") do set GHIDRA_DIR=%%~fi REM Determine if 64-bit or 32-bit if exist "%PROGRAMFILES(X86)%" ( @@ -64,7 +64,7 @@ for /f "tokens=* delims=" %%a in ('dir %arg1% /s /b') do ( setlocal enableDelayedExpansion ( echo "Processing file: %%a" - START /B /WAIT "" "%PDB_EXE%" %%a > "%%a.xml" + START /B /WAIT "" "%PDB_EXE%" "%%a" > "%%a.xml" REM Exit if executable returned non-zero error code (signifies that there is a problem). if !errorlevel! neq 0 ( diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptUtilTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/script/GhidraScriptUtilTest.java similarity index 78% rename from Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptUtilTest.java rename to Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/script/GhidraScriptUtilTest.java index b56bba04a0..c05490e0c3 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/script/GhidraScriptUtilTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/script/GhidraScriptUtilTest.java @@ -15,14 +15,23 @@ */ package ghidra.app.script; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; +import org.junit.Before; import org.junit.Test; import generic.test.AbstractGenericTest; +import ghidra.util.classfinder.ClassSearcher; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.ConsoleTaskMonitor; public class GhidraScriptUtilTest extends AbstractGenericTest { + @Before + public void setup() throws CancelledException { + ClassSearcher.search(false, new ConsoleTaskMonitor()); + } + @Test public void fixupName_WithExtension() { String input = "Bob.java"; @@ -58,4 +67,11 @@ public class GhidraScriptUtilTest extends AbstractGenericTest { String input = "a.b.c.Bob$InnerClass"; assertEquals(GhidraScriptUtil.fixupName(input), "a/b/c/Bob.java"); } + + @Test + public void fixupName_Python() { + String input = "Bob.py"; + assertEquals(GhidraScriptUtil.fixupName(input), "Bob.py"); + } + }