diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java index c407376a93..09da21e3c8 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java @@ -899,4 +899,10 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace super.close(); objectManager.waitWbWorkers(); } + + @Override + protected void domainObjectRestored() { + super.domainObjectRestored(); + dataTypeManager.notifyRestored(); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompEditorModel.java index 897a8dd87d..cdb293ddef 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompEditorModel.java @@ -92,14 +92,24 @@ public abstract class CompEditorModel extends CompositeEditorModel { throw new IllegalStateException( "Can't apply edits without a data type or data type manager."); } - int transactionID = originalDTM.startTransaction("Edit " + getCompositeName()); + boolean originalDtExists = originalDTM.contains(originalDt); + boolean renamed = false; + if (originalDtExists) { + String origName = originalDt.getName(); + String editName = getCompositeName(); + renamed = !origName.equals(editName); + } + String action = originalDtExists ? "Edit" : "Create"; + if (renamed) { + action += "/Rename"; + } + String type = (originalDt instanceof Union) ? " Union " : " Structure "; + int transactionID = originalDTM.startTransaction(action + type + getCompositeName()); try { - if (originalDTM.contains(originalDt)) { - + if (originalDtExists) { // Update the original structure. - String origName = originalDt.getName(); - String editName = getCompositeName(); - if (!origName.equals(editName)) { + if (renamed) { + String editName = getCompositeName(); try { originalDt.setName(editName); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java index 0a0d05cd15..a264661484 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java @@ -545,20 +545,21 @@ public abstract class CompositeEditorPanel extends JPanel } } - public void domainObjectRestored(DataTypeManagerDomainObject domainObject) { + public void dataTypeManagerRestored() { DataTypeManager originalDTM = model.getOriginalDataTypeManager(); if (originalDTM == null) { // editor unloaded return; } boolean reload = true; - String objectType = "domain object"; - if (domainObject instanceof Program) { - objectType = "program"; + String objectType; + if (originalDTM instanceof ProgramBasedDataTypeManager) { + objectType = "Program"; } - else if (domainObject instanceof DataTypeArchive) { - objectType = "data type archive"; + else { + objectType = "Archive"; } + String archiveName = originalDTM.getName(); DataType dt = originalDTM.getDataType(model.getCompositeID()); if (dt instanceof Composite) { Composite composite = (Composite) dt; @@ -570,10 +571,9 @@ public abstract class CompositeEditorPanel extends JPanel Composite originalDt = model.getOriginalComposite(); if (originalDt == null) { provider.show(); - String info = - "The " + objectType + " \"" + domainObject.getName() + "\" has been restored.\n" + - "\"" + model.getCompositeName() + "\" may no longer exist outside the editor."; - Msg.showWarn(this, this, "Program Restored", info); + String info = "The " + objectType + " \"" + archiveName + "\" has been restored.\n" + + "\"" + model.getCompositeName() + "\" may no longer exist outside the editor."; + Msg.showWarn(this, this, objectType + " Restored", info); return; } else if (originalDt.isDeleted()) { @@ -586,8 +586,8 @@ public abstract class CompositeEditorPanel extends JPanel // The user has modified the structure so prompt for whether or // not to reload the structure. String question = - "The " + objectType + " \"" + domainObject.getName() + "\" has been restored.\n" + - "\"" + model.getCompositeName() + "\" may have changed outside the editor.\n" + + "The " + objectType + " \"" + archiveName + "\" has been restored.\n" + "\"" + + model.getCompositeName() + "\" may have changed outside the editor.\n" + "Discard edits & reload the " + model.getTypeName() + "?"; String title = "Reload " + model.getTypeName() + " Editor?"; int response = OptionDialog.showYesNoDialogWithNoAsDefaultButton(this, title, question); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorProvider.java index 3b26fb473b..78e185ed6e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorProvider.java @@ -261,9 +261,8 @@ public abstract class CompositeEditorProvider extends ComponentProviderAdapter return editorModel.hasChanges(); } - @Override - public void domainObjectRestored(DataTypeManagerDomainObject domainObject) { - editorPanel.domainObjectRestored(domainObject); + public void dataTypeManagerRestored() { + editorPanel.dataTypeManagerRestored(); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerDataTypeManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerDataTypeManager.java index e165d9c492..72fced345a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerDataTypeManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerDataTypeManager.java @@ -24,7 +24,7 @@ import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; public class CompositeViewerDataTypeManager extends StandAloneDataTypeManager { - + /** * The data type manager for original composite data type being edited. * This is where the edited datatype will be written back to. @@ -43,7 +43,7 @@ public class CompositeViewerDataTypeManager extends StandAloneDataTypeManager { public CompositeViewerDataTypeManager(String rootName, Composite originalComposite) { super(rootName, originalComposite.getDataTypeManager().getDataOrganization()); this.originalComposite = originalComposite; - transactionID = startTransaction(""); + transactionID = super.startTransaction(""); originalDTM = originalComposite.getDataTypeManager(); ProgramArchitecture arch = originalDTM.getProgramArchitecture(); @@ -68,11 +68,11 @@ public class CompositeViewerDataTypeManager extends StandAloneDataTypeManager { } @Override - public void close() { - endTransaction(transactionID, true); + public void close() { + super.endTransaction(transactionID, true); super.close(); } - + /** * Get the {@link DataTypeManager} associated with the original composite datatype being edited. * @return original datatype manager @@ -82,7 +82,7 @@ public class CompositeViewerDataTypeManager extends StandAloneDataTypeManager { } @Override - public ArchiveType getType() { + public ArchiveType getType() { return originalDTM.getType(); } @@ -103,4 +103,33 @@ public class CompositeViewerDataTypeManager extends StandAloneDataTypeManager { return super.resolve(dataType, handler); } + // + // Transaction support has been disabled since a single open transaction is maintained + // until this DTM is closed. + // + + @SuppressWarnings("sync-override") + @Override + public int startTransaction(String description) { + // ignore - not yet supported + return 0; + } + + @Override + public void endTransaction(int txId, boolean commit) { + // ignore - not yet supported + } + + @SuppressWarnings("sync-override") + @Override + public boolean canUndo() { + return false; + } + + @SuppressWarnings("sync-override") + @Override + public boolean canRedo() { + return false; + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerModel.java index bd1e73dd3a..d57ea5dc01 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerModel.java @@ -959,6 +959,16 @@ abstract class CompositeViewerModel extends AbstractTableModel // Don't care. } + @Override + public void programArchitectureChanged(DataTypeManager dataTypeManager) { + // don't care + } + + @Override + public void restored(DataTypeManager dataTypeManager) { + provider.dataTypeManagerRestored(); + } + //================================================================================================= // Helper methods for CategoryChangeListener methods. //================================================================================================= @@ -1354,8 +1364,4 @@ abstract class CompositeViewerModel extends AbstractTableModel return viewComposite.isPackingEnabled(); } - @Override - public void programArchitectureChanged(DataTypeManager dataTypeManager) { - // don't care - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorProvider.java index 1986a759cf..74f1e32ef2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorProvider.java @@ -47,13 +47,6 @@ public interface EditorProvider { */ public DataTypeManager getDataTypeManager(); - /** - * Notification that the data type manager domain object (program or data type archive) was - * restored. - * @param domainObject the program or data type archive that was restored. - */ - public void domainObjectRestored(DataTypeManagerDomainObject domainObject); - /** * Return whether this editor is editing the data type with the given path. * @param dtPath path of a data type diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorModel.java index 529f3694cb..442662e54e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorModel.java @@ -1264,7 +1264,8 @@ class StructureEditorModel extends CompEditorModel { private DataType createDataTypeInOriginalDTM(StructureDataType structureDataType) { boolean commit = false; DataTypeManager originalDTM = getOriginalDataTypeManager(); - int transactionID = originalDTM.startTransaction("Creating " + structureDataType.getName()); + int transactionID = + originalDTM.startTransaction("Create structure " + structureDataType.getName()); try { DataType addedDataType = originalDTM.addDataType(structureDataType, DataTypeConflictHandler.DEFAULT_HANDLER); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java index 89c6d03c56..eae97887ba 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java @@ -290,7 +290,8 @@ public class DataTypeManagerPlugin extends ProgramPlugin DataTypeManagerDomainObject domainObject = (DataTypeManagerDomainObject) source; provider.domainObjectRestored(domainObject); dataTypePropertyManager.domainObjectRestored(domainObject); - editorManager.domainObjectRestored(domainObject); + // NOTE: each editor that cares about a restored DataTypeManager must establish + // a DataTypeManagerChangeListener and will be notified via the restored method. } } else if (event.contains(DomainObjectEvent.RENAMED)) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeSynchronizer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeSynchronizer.java index cb042212a3..c2c6f8cd69 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeSynchronizer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeSynchronizer.java @@ -99,7 +99,7 @@ public class DataTypeSynchronizer { } private static void update(DataTypeManager refDTM, DataType sourceDT) { - int transactionID = refDTM.startTransaction("Update Datatype"); + int transactionID = refDTM.startTransaction("Update Datatype " + sourceDT.getName()); try { updateAssumingTransactionsOpen(refDTM, sourceDT); } @@ -184,8 +184,7 @@ public class DataTypeSynchronizer { } public void markSynchronized() { - int transactionID = - dataTypeManager.startTransaction("Clear dirty flag for data type manager."); + int transactionID = dataTypeManager.startTransaction("Clear Dirty Flag"); try { sourceArchive.setDirtyFlag(false); sourceArchive.setLastSyncTime(sourceDTM.getLastChangeTimeForMyManager()); @@ -457,8 +456,8 @@ public class DataTypeSynchronizer { return; } - int transactionID = dataTypeManager - .startTransaction("re-sync '" + sourceArchive.getName() + "' data types"); + int transactionID = + dataTypeManager.startTransaction("Sync '" + sourceArchive.getName() + "' data types"); try { reSyncOutOfSyncInTimeOnlyDataTypes(); fixSyncForDifferingDataTypes(); @@ -525,7 +524,7 @@ public class DataTypeSynchronizer { private void autoUpdateDataTypesThatHaveNoRealChanges( List outOfSynchInTimeOnlyList, boolean markArchiveSynchronized) { - int transactionID = dataTypeManager.startTransaction("auto sync datatypes"); + int transactionID = dataTypeManager.startTransaction("Sync datatypes"); try { for (DataTypeSyncInfo dataTypeSyncInfo : outOfSynchInTimeOnlyList) { dataTypeSyncInfo.syncTimes(); @@ -539,16 +538,16 @@ public class DataTypeSynchronizer { } } - public void performBulkOperation(String actionName, List selectedList, + public void performBulkOperation(String actionName, List selectedList, ExceptionalConsumer infoApplier, - Consumer> handleOutOfSync, - boolean sourceRequiresTransaction) throws CancelledException { + Consumer> handleOutOfSync, boolean sourceRequiresTransaction) + throws CancelledException { if (sourceDTM == null) { throw new RuntimeException("Source archive required"); } - - int sourceTransactionId = sourceRequiresTransaction ? - sourceDTM.startTransaction(actionName) : 0; + + int sourceTransactionId = + sourceRequiresTransaction ? sourceDTM.startTransaction(actionName) : 0; int transactionID = dataTypeManager.startTransaction(actionName); try { for (DataTypeSyncInfo info : selectedList) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java index a7594719bc..a7708d1455 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java @@ -181,6 +181,8 @@ public class DataTypesProvider extends ComponentProviderAdapter { // FileEdit group addLocalAction(new LockArchiveAction(plugin)); // Archive addLocalAction(new UnlockArchiveAction(plugin)); // Archive + addLocalAction(new UndoArchiveTransactionAction(plugin)); // Archive + addLocalAction(new RedoArchiveTransactionAction(plugin)); // Archive // Arch group addLocalAction(new SetArchiveArchitectureAction(plugin)); // Archive diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/AbstractTypeDefAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/AbstractTypeDefAction.java index 4c9d9582f4..05a9a4497a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/AbstractTypeDefAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/AbstractTypeDefAction.java @@ -74,7 +74,7 @@ abstract class AbstractTypeDefAction extends DockingAction { private DataType createNewTypeDef(Component parentComponent, TypeDef typedef, CategoryPath categoryPath, DataTypeManager dataTypeManager) { DataType newdt = null; - int transactionID = dataTypeManager.startTransaction("Create Typedef"); + int transactionID = dataTypeManager.startTransaction("Create Typedef " + typedef.getName()); try { newdt = dataTypeManager.addDataType(typedef, plugin.getConflictHandler()); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/AbstractUndoRedoArchiveTransactionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/AbstractUndoRedoArchiveTransactionAction.java new file mode 100644 index 0000000000..1d36502b2d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/AbstractUndoRedoArchiveTransactionAction.java @@ -0,0 +1,150 @@ +/* ### + * 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.datamgr.actions; + +import javax.swing.tree.TreePath; + +import org.apache.commons.lang3.StringUtils; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.MenuData; +import docking.widgets.tree.GTree; +import docking.widgets.tree.GTreeNode; +import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; +import ghidra.app.plugin.core.datamgr.DataTypesActionContext; +import ghidra.app.plugin.core.datamgr.tree.*; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.StandAloneDataTypeManager; + +public abstract class AbstractUndoRedoArchiveTransactionAction extends DockingAction { + + private String actionName; // Undo / Redo + + /** + * Construct Undo/Redo action + * @param actionName "Undo" or "Redo" action name + * @param plugin {@link DataTypeManagerPlugin} + */ + public AbstractUndoRedoArchiveTransactionAction(String actionName, + DataTypeManagerPlugin plugin) { + super(actionName + " Archive Change", plugin.getName()); + this.actionName = actionName; + setPopupMenuData(getMenuData(null)); + setEnabled(true); + } + + private MenuData getMenuData(String txName) { + String name = actionName + " Change"; + if (!StringUtils.isEmpty(txName)) { + name += ": " + txName; + } + return new MenuData(new String[] { name }, null, "FileEdit"); + } + + @Override + public boolean isAddToPopup(ActionContext context) { + if (!(context instanceof DataTypesActionContext)) { + return false; + } + + TreePath[] selectionPaths = getSelectionPaths(context); + return getModifiableProjectOrFileDTM(selectionPaths) != null; + } + + /** + * Determine if the corresponding undo/redo can be performed + * @param dtm archive datatype manager + * @return true if action can be performed on archive + */ + abstract protected boolean canExecute(StandAloneDataTypeManager dtm); + + /** + * Determine the next undo/redo transaction name + * @param dtm archive datatype manager + * @return next undo/redo transaction name + */ + abstract protected String getNextName(StandAloneDataTypeManager dtm); + + /** + * Execute the undo/redo operation on the specified archive datatype manager. + * @param dtm archive datatype manager + */ + abstract protected void execute(StandAloneDataTypeManager dtm); + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (!(context instanceof DataTypesActionContext)) { + return false; + } + + TreePath[] selectionPaths = getSelectionPaths(context); + StandAloneDataTypeManager dtm = getModifiableProjectOrFileDTM(selectionPaths); + if (dtm != null && canExecute(dtm)) { + setPopupMenuData(getMenuData(getNextName(dtm))); + return true; + } + setPopupMenuData(getMenuData(null)); + return false; + } + + @Override + public void actionPerformed(ActionContext context) { + + if (!(context instanceof DataTypesActionContext)) { + return; + } + + TreePath[] selectionPaths = getSelectionPaths(context); + StandAloneDataTypeManager dtm = getModifiableProjectOrFileDTM(selectionPaths); + if (dtm != null && canExecute(dtm)) { + execute(dtm); + } + } + + private TreePath[] getSelectionPaths(ActionContext context) { + Object contextObject = context.getContextObject(); + GTree gtree = (GTree) contextObject; + TreePath[] selectionPaths = gtree.getSelectionPaths(); + return selectionPaths; + } + + private StandAloneDataTypeManager getModifiableProjectOrFileDTM(TreePath[] selectionPaths) { + // only valid if single file or project archive node is selected + if (selectionPaths.length != 1) { + return null; + } + + TreePath path = selectionPaths[0]; + if (path.getPathCount() < 2) { + return null; + } + + GTreeNode node = (GTreeNode) path.getPathComponent(1); + if (!(node instanceof FileArchiveNode) && !(node instanceof ProjectArchiveNode)) { + return null; + } + + ArchiveNode archiveNode = (ArchiveNode) node; + if (archiveNode.isModifiable()) { + DataTypeManager dtm = archiveNode.getArchive().getDataTypeManager(); + if (dtm instanceof StandAloneDataTypeManager archiveDtm) { + return archiveDtm; + } + } + return null; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreateCategoryAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreateCategoryAction.java index 77bc201f59..02853f7224 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreateCategoryAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreateCategoryAction.java @@ -26,8 +26,7 @@ import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; import ghidra.app.plugin.core.datamgr.DataTypesActionContext; import ghidra.app.plugin.core.datamgr.archive.Archive; import ghidra.app.plugin.core.datamgr.tree.*; -import ghidra.program.model.data.Category; -import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.*; import ghidra.util.InvalidNameException; public class CreateCategoryAction extends DockingAction { @@ -90,10 +89,10 @@ public class CreateCategoryAction extends DockingAction { Archive archive = archiveNode.getArchive(); DataTypeManager dataTypeManager = archive.getDataTypeManager(); - String newNodeName = null; - int transactionID = dataTypeManager.startTransaction("Create Category"); + String newNodeName = getUniqueCategoryName(category); + String path = category.toString() + newNodeName; + int transactionID = dataTypeManager.startTransaction("Create " + path); try { - newNodeName = getUniqueCategoryName(category); category.createCategory(newNodeName); } catch (InvalidNameException ie) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreatePointerAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreatePointerAction.java index 227153fa26..f4132c1554 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreatePointerAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreatePointerAction.java @@ -78,7 +78,8 @@ public class CreatePointerAction extends DockingAction { private DataType createNewDataType(Component parentComponent, DataType dataType, CategoryPath categoryPath, DataTypeManager dataTypeManager) { - int transactionID = dataTypeManager.startTransaction("Create Typedef"); + int transactionID = + dataTypeManager.startTransaction("Create Pointer " + dataType.getName()); try { return dataTypeManager.addDataType(dataType, plugin.getConflictHandler()); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/Pack1DataTypeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/Pack1DataTypeAction.java index def536c39c..06790fcb92 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/Pack1DataTypeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/Pack1DataTypeAction.java @@ -79,7 +79,7 @@ public class Pack1DataTypeAction extends DockingAction { boolean commit = false; try { // start a transaction - transactionID = dataTypeManager.startTransaction("pack of " + dataType.getName()); + transactionID = dataTypeManager.startTransaction("Pack(1) " + dataType.getName()); packDataType(dataType); commit = true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackDataTypeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackDataTypeAction.java index ec93f82c93..54ce96245c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackDataTypeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackDataTypeAction.java @@ -29,7 +29,6 @@ import ghidra.util.Msg; public class PackDataTypeAction extends DockingAction { - public PackDataTypeAction(DataTypeManagerPlugin plugin) { super("Pack Data Type", plugin.getName()); setPopupMenuData(new MenuData(new String[] { "Pack (default)" }, "Edit")); @@ -100,20 +99,20 @@ public class PackDataTypeAction extends DockingAction { private void alignDataType(DataType dataType, DataOrganization dataOrganization) { DataTypeManager dataTypeManager = dataType.getDataTypeManager(); if (dataTypeManager == null) { - Msg.error(this, "Can't align data type " + dataType.getName() + - " without a data type manager."); + Msg.error(this, + "Can't align data type " + dataType.getName() + " without a data type manager."); return; } if (!(dataType instanceof Structure)) { - Msg.error(this, "Can't align data type " + dataType.getName() + - ". It's not a structure."); + Msg.error(this, + "Can't align data type " + dataType.getName() + ". It's not a structure."); return; } int transactionID = -1; boolean commit = false; try { // start a transaction - transactionID = dataTypeManager.startTransaction("align " + dataType.getName()); + transactionID = dataTypeManager.startTransaction("Pack " + dataType.getName()); ((Structure) dataType).setPackingEnabled(true); commit = true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackSizeDataTypeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackSizeDataTypeAction.java index ee538ddb98..e6a77569f6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackSizeDataTypeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/PackSizeDataTypeAction.java @@ -84,7 +84,7 @@ public class PackSizeDataTypeAction extends DockingAction { try { // start a transaction transactionID = - dataTypeManager.startTransaction("pack(" + packSize + ") of " + dataType.getName()); + dataTypeManager.startTransaction("Pack(" + packSize + ") " + dataType.getName()); packDataType(dataType, packSize); commit = true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/RedoArchiveTransactionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/RedoArchiveTransactionAction.java new file mode 100644 index 0000000000..cf9345eadf --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/RedoArchiveTransactionAction.java @@ -0,0 +1,45 @@ +/* ### + * 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.datamgr.actions; + +import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; +import ghidra.program.model.data.StandAloneDataTypeManager; + +public class RedoArchiveTransactionAction extends AbstractUndoRedoArchiveTransactionAction { + + public RedoArchiveTransactionAction(DataTypeManagerPlugin plugin) { + super("Redo", plugin); + // Key-bind disabled by default to activation context concerns + //setKeyBindingData(new KeyBindingData("ctrl shift Z")); + setDescription("Redo last undone change made to data type archive"); + } + + @Override + protected boolean canExecute(StandAloneDataTypeManager dtm) { + return dtm.canRedo(); + } + + @Override + protected String getNextName(StandAloneDataTypeManager dtm) { + return dtm.getRedoName(); + } + + @Override + protected void execute(StandAloneDataTypeManager dtm) { + dtm.redo(); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/UndoArchiveTransactionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/UndoArchiveTransactionAction.java new file mode 100644 index 0000000000..bc16e07226 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/UndoArchiveTransactionAction.java @@ -0,0 +1,45 @@ +/* ### + * 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.datamgr.actions; + +import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; +import ghidra.program.model.data.StandAloneDataTypeManager; + +public class UndoArchiveTransactionAction extends AbstractUndoRedoArchiveTransactionAction { + + public UndoArchiveTransactionAction(DataTypeManagerPlugin plugin) { + super("Undo", plugin); + // Key-bind disabled by default to activation context concerns + //setKeyBindingData(new KeyBindingData("ctrl Z")); + setDescription("Undo last change made to data type archive"); + } + + @Override + protected boolean canExecute(StandAloneDataTypeManager dtm) { + return dtm.canUndo(); + } + + @Override + protected String getNextName(StandAloneDataTypeManager dtm) { + return dtm.getUndoName(); + } + + @Override + protected void execute(StandAloneDataTypeManager dtm) { + dtm.undo(); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/associate/AssociateDataTypeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/associate/AssociateDataTypeAction.java index 20e1a7c2b4..0e7fe9bfa1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/associate/AssociateDataTypeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/associate/AssociateDataTypeAction.java @@ -282,7 +282,8 @@ public class AssociateDataTypeAction extends DockingAction { } boolean noErrors = false; - int tx = dtm.startTransaction("Create Category"); + String path = archive.getName() + categoryPath; + int tx = dtm.startTransaction("Create " + path); try { category = dtm.createCategory(categoryPath); noErrors = true; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/associate/SyncAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/associate/SyncAction.java index 6044ad6868..89cc9ffe4a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/associate/SyncAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/associate/SyncAction.java @@ -131,7 +131,8 @@ public abstract class SyncAction extends DockingAction implements Comparable outOfSynchDataTypes = synchronizer.findOutOfSynchDataTypes(); - removeAndUpdateOutOfSyncInTimeOnlyDataTypes(synchronizer, synchronizer.findOutOfSynchDataTypes()); + removeAndUpdateOutOfSyncInTimeOnlyDataTypes(synchronizer, + synchronizer.findOutOfSynchDataTypes()); if (outOfSynchDataTypes.isEmpty()) { showNoDataTypesToSyncMessage(); return; @@ -194,20 +195,20 @@ public abstract class SyncAction extends DockingAction implements Comparable selectedList, List outOfSynchDataTypes, TaskMonitor monitor) throws CancelledException { - + synchronizer.performBulkOperation(getName(), selectedList, info -> { monitor.checkCancelled(); monitor.setMessage("Syncing " + info.getName()); applyOperation(info); outOfSynchDataTypes.remove(info); monitor.incrementProgress(1); - + }, outOfSyncList -> { // dataTypeChanged can cause other related data types to become updated // and their times will appear out of sync. So clean up any that actually // are the same. removeAndUpdateOutOfSyncInTimeOnlyDataTypes(synchronizer, outOfSyncList); - + }, requiresArchiveOpenForEditing()); } @@ -326,7 +327,7 @@ public abstract class SyncAction extends DockingAction implements Comparable outOfSynchInTimeOnlyList, boolean markArchiveSynchronized) { - int transactionID = dtm.startTransaction("Auto-sync data types"); + int transactionID = dtm.startTransaction("Sync data types"); try { for (DataTypeSyncInfo dataTypeSyncInfo : outOfSynchInTimeOnlyList) { dataTypeSyncInfo.syncTimes(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeIndexer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeIndexer.java index 8b44e7bc65..80fc6964ff 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeIndexer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeIndexer.java @@ -220,5 +220,10 @@ public class DataTypeIndexer { public void programArchitectureChanged(DataTypeManager dataTypeManager) { markStale(); } + + @Override + public void restored(DataTypeManager dataTypeManager) { + markStale(); + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java index df310c70c9..3449a803f9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/DataTypeManagerHandler.java @@ -640,7 +640,6 @@ public class DataTypeManagerHandler { void dataTypeManagerChanged(FileArchive archive, DataTypeManager oldManager, DataTypeManager newManager) { - oldManager.removeDataTypeManagerListener(listenerDelegate); newManager.addDataTypeManagerListener(listenerDelegate); dataTypeIndexer.removeDataTypeManager(oldManager); @@ -1239,6 +1238,13 @@ public class DataTypeManagerHandler { listener.programArchitectureChanged(dataTypeManager); } } + + @Override + public void restored(DataTypeManager dataTypeManager) { + for (DataTypeManagerChangeListener listener : dataTypeManagerListeners) { + listener.restored(dataTypeManager); + } + } } /** @@ -1412,8 +1418,7 @@ public class DataTypeManagerHandler { } private DataTreeDialog getSaveDialog() { - DataTreeDialog dialog = - new DataTreeDialog(null, "Save As", SAVE, createArchiveFileFilter); + DataTreeDialog dialog = new DataTreeDialog(null, "Save As", SAVE, createArchiveFileFilter); ActionListener listener = event -> { DomainFolder folder = dialog.getDomainFolder(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/FileArchive.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/FileArchive.java index 3e4e2e851a..8ea1db5e57 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/FileArchive.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/FileArchive.java @@ -300,6 +300,12 @@ public class FileArchive implements Archive { public void programArchitectureChanged(DataTypeManager dataTypeManager) { setChanged(true); } + + @Override + public void restored(DataTypeManager dataTypeManager) { + archiveManager.dataTypeManagerChanged(FileArchive.this, dataTypeManager, + dataTypeManager); + } } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/ProjectArchive.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/ProjectArchive.java index 7546a4cbaa..fc3cb58c1e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/ProjectArchive.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/archive/ProjectArchive.java @@ -29,7 +29,7 @@ public class ProjectArchive implements DomainFileArchive { private static Icon CLOSED_ICON = new GIcon("icon.plugin.datatypes.archive.project.closed"); private static Icon OPEN_ICON = new GIcon("icon.plugin.datatypes.archive.project.open"); - + private DataTypeArchive dataTypeArchive; private DomainFile sourceDomainFile; private DataTypeManagerChangeListener categoryListener; // hold on to since it is stored in a weak set @@ -67,6 +67,7 @@ public class ProjectArchive implements DomainFileArchive { return -1; // Project Archives appear between the ProgramArchive and FileArchives. } + @Override public boolean hasExclusiveAccess() { return dataTypeArchive.hasExclusiveAccess(); } @@ -202,5 +203,10 @@ public class ProjectArchive implements DomainFileArchive { public void programArchitectureChanged(DataTypeManager dtm) { fireStateChanged(); } + + @Override + public void restored(DataTypeManager dtm) { + fireStateChanged(); + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/DataTypeEditorManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/DataTypeEditorManager.java index ec8772bff8..e5693e84b2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/DataTypeEditorManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/DataTypeEditorManager.java @@ -390,34 +390,6 @@ public class DataTypeEditorManager implements EditorListener { return false; } - public void domainObjectRestored(DataTypeManagerDomainObject domainObject) { - // Create a copy of the list since restore may remove an editor from the original list. - ArrayList list = new ArrayList<>(editorList); - // notify the editors - for (EditorProvider editor : list) { - DataTypeManager dataTypeManager = editor.getDataTypeManager(); - DataTypeManager programDataTypeManager = domainObject.getDataTypeManager(); - if (dataTypeManager == programDataTypeManager) { - /* - - It is not clear why this check was added. It seem reasonable to always let the - editor know about the event. With this code enabled, editors with new, unsaved - types will be closed. - - DataTypePath dtPath = editor.getDtPath(); - CategoryPath categoryPath = dtPath.getCategoryPath(); - String name = dtPath.getDataTypeName(); - DataType dataType = programDataTypeManager.getDataType(categoryPath, name); - if (dataType == null || dataType.isDeleted()) { - dismissEditor(editor); - continue; - } - */ - editor.domainObjectRestored(domainObject); - } - } - } - /** * If the specified data type is being edited for the indicated category, this gets that editor. * @param dataType the data type diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java index e49af538c0..dd1c3a1518 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorPanel.java @@ -36,6 +36,7 @@ import docking.widgets.textfield.GValidatedTextField.ValidationMessageListener; import generic.theme.Gui; import ghidra.docking.settings.Settings; import ghidra.program.model.data.*; +import ghidra.program.model.data.Enum; import ghidra.program.model.listing.DataTypeArchive; import ghidra.program.model.listing.Program; import ghidra.util.*; @@ -120,30 +121,40 @@ class EnumEditorPanel extends JPanel { }); } - void domainObjectRestored(DataTypeManagerDomainObject domainObject, EnumDataType enuum) { + void domainObjectRestored(EnumDataType enuum, boolean exists) { stopCellEditing(); - this.originalEnumDT = enuum; - this.editedEnumDT = (EnumDataType) enuum.copy(enuum.getDataTypeManager()); - DataTypeManager objectDataTypeManager = domainObject.getDataTypeManager(); - DataTypeManager providerDataTypeManager = provider.getDataTypeManager(); - if (objectDataTypeManager != providerDataTypeManager) { - return; // The editor isn't associated with the restored domain object. - } + DataTypeManager enumDtMgr = enuum.getDataTypeManager(); String objectType = "domain object"; - if (domainObject instanceof Program) { + if (enumDtMgr instanceof ProgramBasedDataTypeManager) { objectType = "program"; } - else if (domainObject instanceof DataTypeArchive) { + else { objectType = "data type archive"; } + String archiveName = enumDtMgr.getName(); + this.originalEnumDT = enuum; - if (tableModel.hasChanges()) { + if (!exists) { + if (OptionDialog.showOptionNoCancelDialog(this, "Close Enum Editor?", + "The " + objectType + " \"" + archiveName + "\" has been restored.\n" + "\"" + + enuum.getDisplayName() + "\" may no longer exist outside the editor.\n" + + "Do you want to close editor?", + "Close", "Continue Edit", + OptionDialog.WARNING_MESSAGE) == OptionDialog.OPTION_ONE) { + provider.dispose(); + } + else { + provider.stateChanged(null); + } + return; + } + + if (exists && tableModel.hasChanges()) { if (OptionDialog.showYesNoDialogWithNoAsDefaultButton(this, "Reload Enum Editor?", - "The " + objectType + " \"" + objectDataTypeManager.getName() + - "\" has been restored.\n" + "\"" + tableModel.getEnum().getDisplayName() + - "\" may have changed outside this editor.\n" + + "The " + objectType + " \"" + archiveName + "\" has been restored.\n" + "\"" + + enuum.getDisplayName() + "\" may have changed outside this editor.\n" + "Do you want to discard edits and reload the Enum?") == OptionDialog.OPTION_TWO) { // 'No'; do not discard @@ -153,6 +164,7 @@ class EnumEditorPanel extends JPanel { } // reload the enum + this.editedEnumDT = (EnumDataType) enuum.copy(enuum.getDataTypeManager()); setFieldInfo(editedEnumDT); tableModel.setEnum(editedEnumDT, false); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorProvider.java index f5a3ae5b6b..ec4f8ff178 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/editor/EnumEditorProvider.java @@ -107,6 +107,7 @@ public class EnumEditorProvider extends ComponentProviderAdapter } originalCategoryPath = categoryPath; originalEnum = enumDT; + originalEnumName = enumDT.getDisplayName(); dataTypeManager = enumDTM; @@ -213,25 +214,6 @@ public class EnumEditorProvider extends ComponentProviderAdapter return editorPanel.needsSave(); } - @Override - public void domainObjectRestored(DataTypeManagerDomainObject domainObject) { - - if (originalEnumID == -1) { - return; - } - - Enum enuum = (Enum) dataTypeManager.getDataType(originalEnumID); - if (enuum != null) { - EnumDataType dt = (EnumDataType) enuum.copy(dataTypeManager); - originalEnumName = dt.getDisplayName(); - updateTitle(dt); - Category category = dataTypeManager.getCategory(enuum.getCategoryPath()); - originalCategoryPath = category.getCategoryPath(); - editorPanel.domainObjectRestored(domainObject, dt); - } - tool.setStatusInfo(""); - } - @Override public boolean isTransient() { return true; @@ -348,11 +330,21 @@ public class EnumEditorProvider extends ComponentProviderAdapter setStatusMessage("Empty enum is not allowed"); return false; } - int txID = startTransaction(); + boolean originalDtExists = dataTypeManager.contains(originalEnum); + boolean renamed = false; + if (originalDtExists) { + String editorName = editorPanel.getEnumName().trim(); + renamed = !originalEnumName.equals(editorName); + } + String action = originalDtExists ? "Edit" : "Create"; + if (renamed) { + action += "/Rename"; + } + int txID = dataTypeManager.startTransaction(action + " Enum " + editedEnum.getName()); try { - DataTypeManager dtm = editedEnum.getDataTypeManager(); - boolean userSaved = resolveEquateConflicts(editedEnum, dtm); + + boolean userSaved = resolveEquateConflicts(editedEnum); if (!userSaved) { return false; } @@ -364,11 +356,12 @@ public class EnumEditorProvider extends ComponentProviderAdapter newEnuum.replaceWith(editedEnum); originalEnum = newEnuum; + originalEnumID = dataTypeManager.getID(newEnuum); editorPanel.setEnum((EnumDataType) newEnuum.copy(dataTypeManager)); applyAction.setEnabled(hasChanges()); } finally { - endTransaction(txID); + dataTypeManager.endTransaction(txID, true); } return true; } @@ -381,10 +374,9 @@ public class EnumEditorProvider extends ComponentProviderAdapter /** * Checks to see if the new changes to the enum will affect equates based off of it. * @param editedEnum the enum to check for conflicts with - * @param dtm the data type manager that this enum lies within * @return true if the enum should save its changes; otherwise, false */ - private boolean resolveEquateConflicts(Enum editedEnum, DataTypeManager dtm) { + private boolean resolveEquateConflicts(Enum editedEnum) { Program program = plugin.getProgram(); if (program == null) { @@ -500,14 +492,6 @@ public class EnumEditorProvider extends ComponentProviderAdapter } } - private int startTransaction() { - return dataTypeManager.startTransaction("Edit Enum"); - } - - private void endTransaction(int transID) { - dataTypeManager.endTransaction(transID, true); - } - /** * Prompts the user if the editor has unsaved changes. Saves the changes if * the user indicates to do so. @@ -692,6 +676,36 @@ public class EnumEditorProvider extends ComponentProviderAdapter dispose(); } + @Override + public void restored(DataTypeManager dtm) { + if (originalEnumID <= 0) { + return; + } + + DataTypeManager originalDTM = originalEnum.getDataTypeManager(); + DataType dt = originalDTM.getDataType(originalEnumID); + + boolean exists = false; + if (dt instanceof Enum) { + originalEnum = (Enum) dt; + exists = true; + } + else { + // original enum no longer exists + originalEnumID = -1; + EnumDataType enuum = editorPanel.getEnum(); + originalEnum = new EnumDataType(enuum.getCategoryPath(), enuum.getName(), + enuum.getLength(), originalDTM); + } + + originalEnumName = originalEnum.getDisplayName(); + updateTitle(originalEnum); + originalCategoryPath = originalEnum.getCategoryPath(); + + editorPanel.domainObjectRestored((EnumDataType) originalEnum.copy(originalDTM), exists); + tool.setStatusInfo(""); + } + private boolean isMyCategory(DataTypePath path) { CategoryPath parentPath = path.getCategoryPath(); return parentPath.equals(originalCategoryPath); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveNode.java index a7e571bcc7..df03d15268 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/ArchiveNode.java @@ -480,5 +480,13 @@ public class ArchiveNode extends CategoryNode { unloadChildren(); nodeChangedUpdater.update(); } + + @Override + public void restored(DataTypeManager manager) { + // need to force all cached datatype tooltips to be cleared + // due to potential changes (e.g., undo/redo) + unloadChildren(); + nodeChangedUpdater.update(); + } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/CategoryNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/CategoryNode.java index 43cfcc02d9..f9abbf89ab 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/CategoryNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/CategoryNode.java @@ -253,18 +253,20 @@ public class CategoryNode extends DataTypeTreeNode { @Override public void valueChanged(Object newValue) { - int transactionID = category.getDataTypeManager().startTransaction("rename"); + String newName = newValue.toString(); + int transactionID = + category.getDataTypeManager().startTransaction("Rename Category " + newName); try { - category.setName(newValue.toString()); + category.setName(newName); } catch (DuplicateNameException e) { Msg.showError(getClass(), null, "Rename Failed", - "Category by the name " + newValue + " already exists in this category."); + "Category by the name " + newName + " already exists in this category."); } catch (InvalidNameException exc) { String msg = exc.getMessage(); if (msg == null) { - msg = "Invalid name specified: " + newValue; + msg = "Invalid name specified: " + newName; } Msg.showError(getClass(), null, "Invalid name specified", exc.getMessage()); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeNode.java index 013c472add..0b1d25cab4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/DataTypeNode.java @@ -140,7 +140,7 @@ public class DataTypeNode extends DataTypeTreeNode { return; } - int transactionID = dataType.getDataTypeManager().startTransaction("rename"); + int transactionID = dataType.getDataTypeManager().startTransaction("Rename DataType"); try { dataType.setName(newName); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datapreview/DataTypePreviewPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datapreview/DataTypePreviewPlugin.java index ceab354645..ffc179a131 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datapreview/DataTypePreviewPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datapreview/DataTypePreviewPlugin.java @@ -182,7 +182,7 @@ public class DataTypePreviewPlugin extends ProgramPlugin { DataTypeManager newDtm = createLayeredDataTypeManager(); - int transactionId = newDtm.startTransaction("add datatypes"); + int transactionId = newDtm.startTransaction("Add Datatypes"); try { Iterator allDataTypes = dataTypeManager.getAllDataTypes(); while (allDataTypes.hasNext()) { @@ -343,7 +343,7 @@ public class DataTypePreviewPlugin extends ProgramPlugin { return; } - int transactionID = dataTypeManager.startTransaction("Add dataType"); + int transactionID = dataTypeManager.startTransaction("Add " + dt.getName()); try { DataType resolvedDt = dataTypeManager.resolve(dt, null); model.add(resolvedDt); @@ -354,7 +354,7 @@ public class DataTypePreviewPlugin extends ProgramPlugin { } private void removeDataType(DataType dt) { - int transactionID = dataTypeManager.startTransaction("Remove dataType"); + int transactionID = dataTypeManager.startTransaction("Remove " + dt.getName()); try { model.removeAll(dt); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java index 7da4360a69..ca45b6fa2e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java @@ -24,7 +24,8 @@ import javax.swing.*; import docking.widgets.OptionDialog; import ghidra.app.plugin.core.compositeeditor.CompositeEditorPanel; import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.data.*; +import ghidra.program.model.data.Composite; +import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.listing.*; import ghidra.util.exception.UsrException; @@ -273,15 +274,9 @@ public class StackEditorPanel extends CompositeEditorPanel { } @Override - public void domainObjectRestored(DataTypeManagerDomainObject domainObject) { + public void dataTypeManagerRestored() { boolean reload = true; - String objectType = "domain object"; - if (domainObject instanceof Program) { - objectType = "program"; - } - else if (domainObject instanceof DataTypeArchive) { - objectType = "data type archive"; - } + String objectType = "program"; DataTypeManager dtm = ((StackEditorModel) model).getOriginalDataTypeManager(); Composite originalDt = ((StackEditorModel) model).getOriginalComposite(); if (originalDt instanceof StackFrameDataType) { @@ -306,8 +301,8 @@ public class StackEditorPanel extends CompositeEditorPanel { // The user has modified the structure so prompt for whether or // not to reload the structure. String question = - "The " + objectType + " \"" + domainObject.getName() + "\" has been restored.\n" + - "\"" + model.getCompositeName() + "\" may have changed outside the editor.\n" + + "The " + objectType + " \"" + dtm.getName() + "\" has been restored.\n" + "\"" + + model.getCompositeName() + "\" may have changed outside the editor.\n" + "Discard edits & reload the " + name + " Editor?"; String title = "Reload " + name + " Editor?"; int response = OptionDialog.showYesNoDialogWithNoAsDefaultButton(this, title, question); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider.java index 1a6bca1df5..0db383d55a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider.java @@ -23,7 +23,8 @@ import ghidra.app.plugin.core.compositeeditor.*; import ghidra.framework.model.*; import ghidra.framework.plugintool.Plugin; import ghidra.program.model.address.Address; -import ghidra.program.model.data.*; +import ghidra.program.model.data.CategoryPath; +import ghidra.program.model.data.DataTypePath; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.Symbol; @@ -144,12 +145,6 @@ public class StackEditorProvider extends CompositeEditorProvider implements Doma return actionMgr.getAllActions(); } - @Override - public void domainObjectRestored(DataTypeManagerDomainObject domainObject) { - refreshName(); - editorPanel.domainObjectRestored(domainObject); - } - private void refreshName() { StackFrameDataType origDt = (StackFrameDataType) stackModel.getOriginalComposite(); StackFrameDataType viewDt = stackModel.getViewComposite(); @@ -187,11 +182,9 @@ public class StackEditorProvider extends CompositeEditorProvider implements Doma DomainObjectChangeRecord rec = event.getChangeRecord(i); EventType eventType = rec.getEventType(); if (eventType == DomainObjectEvent.RESTORED) { - Object source = event.getSource(); - if (source instanceof Program) { - Program restoredProgram = (Program) source; - domainObjectRestored(restoredProgram); - } + refreshName(); + // NOTE: editorPanel should be notified of restored datatype manager via the + // CompositeViewerModel's DataTypeManagerChangeListener restored method return; } if (eventType instanceof ProgramEvent type) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java index 5c465aa99c..819f563831 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/golang/rtti/GoRttiMapper.java @@ -460,7 +460,6 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex return result; } - /** * Returns the golang version * @return {@link GoVer} @@ -842,9 +841,8 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex // gdt data base. This method only leaves the target gdt filename + ".step1" in the db. File tmpGDTFile = new File(gdtFile.getParentFile(), gdtFile.getName() + ".step1.gdt"); FileDataTypeManager tmpFdtm = FileDataTypeManager.createFileArchive(tmpGDTFile); - int tx = -1; + int tx = tmpFdtm.startTransaction("Import"); try { - tx = tmpFdtm.startTransaction("Import"); tmpFdtm.addDataTypes(registeredStructDTs, DataTypeConflictHandler.DEFAULT_HANDLER, monitor); if (runtimeFuncSnapshot) { @@ -879,17 +877,14 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex Msg.error(this, "Error when exporting types to file: %s".formatted(gdtFile), e); } finally { - if (tx != -1) { - tmpFdtm.endTransaction(tx, true); - } + tmpFdtm.endTransaction(tx, true); } tmpFdtm.save(); FileDataTypeManager fdtm = FileDataTypeManager.createFileArchive(gdtFile); - tx = -1; + tx = fdtm.startTransaction("Import"); try { - tx = fdtm.startTransaction("Import"); tmpFdtm.getAllDataTypes() .forEachRemaining( dt -> fdtm.addDataType(dt, DataTypeConflictHandler.DEFAULT_HANDLER)); @@ -898,9 +893,7 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex } } finally { - if (tx != -1) { - fdtm.endTransaction(tx, true); - } + fdtm.endTransaction(tx, true); } fdtm.save(); @@ -926,7 +919,6 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex return existingDT; } - private List createBootstrapFuncDefs(DataTypeManager destDTM, CategoryPath destCP, TaskMonitor monitor) throws CancelledException { List funcs = getAllFunctions().stream() @@ -959,7 +951,6 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex return results; } - private void moveAllDataTypesTo(DataTypeManager dtm, CategoryPath srcCP, CategoryPath destCP) throws DuplicateNameException, DataTypeDependencyException, InvalidNameException { Category srcCat = dtm.getCategory(srcCP); @@ -1182,7 +1173,7 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex .map(Entry::getKey) .collect(toSet()); typeDupCount.clear(); - + for (GoType goType : goTypes.values()) { String typeName = goType.getNameWithPackageString(); if (dupedTypeNames.contains(typeName)) { @@ -1270,10 +1261,11 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex StructureContext structContext = getStructureContextOfInstance(structInstance); String fallbackName = defaultValue; - fallbackName = fallbackName == null && structContext != null - ? "%s_%x".formatted(structContext.getMappingInfo().getStructureName(), - structContext.getStructureStart()) - : "invalid_object"; + fallbackName = + fallbackName == null && structContext != null + ? "%s_%x".formatted(structContext.getMappingInfo().getStructureName(), + structContext.getStructureStart()) + : "invalid_object"; return GoName.createFakeInstance(fallbackName); } @@ -1296,7 +1288,6 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex return "unknown_type_%x".formatted(offset); } - /** * Returns the {@link GoType} corresponding to an offset that is relative to the controlling * GoModuledata's typesOffset. @@ -1441,15 +1432,13 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex private AddressRange getPclntabSearchRange() { MemoryBlock memBlock = getFirstGoSection(program, "noptrdata", "rdata"); - return memBlock != null - ? new AddressRangeImpl(memBlock.getStart(), memBlock.getEnd()) + return memBlock != null ? new AddressRangeImpl(memBlock.getStart(), memBlock.getEnd()) : null; } private AddressRange getModuledataSearchRange() { MemoryBlock memBlock = getFirstGoSection(program, "noptrdata", "data"); - return memBlock != null - ? new AddressRangeImpl(memBlock.getStart(), memBlock.getEnd()) + return memBlock != null ? new AddressRangeImpl(memBlock.getStart(), memBlock.getEnd()) : null; } @@ -1489,7 +1478,6 @@ public class GoRttiMapper extends DataTypeMapper implements DataTypeMapperContex return result; } - public Symbol getGoSymbol(String symbolName) { return getGoSymbol(program, symbolName); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/DataTypeCleaner.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/DataTypeCleaner.java index 53b0a82905..f9c0dcce3d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/DataTypeCleaner.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/DataTypeCleaner.java @@ -56,7 +56,7 @@ public class DataTypeCleaner implements Closeable { this.targetDtm = targetDtm; this.retainExistingComposites = retainExistingComposites; this.cleanerDtm = new StandAloneDataTypeManager("CleanerDTM"); - txId = cleanerDtm.startTransaction("CleanerTx"); + txId = cleanerDtm.startTransaction("Clean Datatypes"); ProgramArchitecture arch = targetDtm.getProgramArchitecture(); if (arch != null) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/AbstractEditorTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/AbstractEditorTest.java index 127e5f58a7..448f89aca1 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/AbstractEditorTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/AbstractEditorTest.java @@ -40,7 +40,6 @@ import ghidra.app.plugin.core.datamgr.util.DataTypeChooserDialog; import ghidra.app.plugin.core.stackeditor.StackEditorModel; import ghidra.app.services.DataTypeManagerService; import ghidra.app.util.datatype.DataTypeSelectionEditor; -import ghidra.framework.model.*; import ghidra.framework.options.Options; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginException; @@ -488,20 +487,6 @@ public abstract class AbstractEditorTest extends AbstractGhidraHeadedIntegration program.endTransaction(txId, saveChanges); } - protected class RestoreListener implements DomainObjectListener { - @Override - public void domainObjectChanged(DomainObjectChangedEvent event) { - if (event.contains(DomainObjectEvent.RESTORED)) { - Object source = event.getSource(); - if (source instanceof DataTypeManagerDomainObject) { - DataTypeManagerDomainObject restoredDomainObject = - (DataTypeManagerDomainObject) source; - provider.domainObjectRestored(restoredDomainObject); - } - } - } - } - protected class StatusListener extends CompositeEditorModelAdapter { String status = null; boolean beep = false; diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProviderTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProviderTest.java index 4d4cb788b3..ffc556bc26 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProviderTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProviderTest.java @@ -108,11 +108,9 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { // Test Undo / Redo of program. @Test public void testModifiedDtAndProgramRestored() throws Exception { - RestoreListener restoreListener = new RestoreListener(); Window dialog; try { init(complexStructure, pgmTestCat, false); - program.addListener(restoreListener); // Change the structure runSwingLater(() -> { @@ -165,7 +163,6 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { } finally { dialog = null; - program.removeListener(restoreListener); } } @@ -173,7 +170,6 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { // This should close the edit session. @Test public void testProgramRestoreRemovesEditedDt() throws Exception { - RestoreListener restoreListener = new RestoreListener(); Window dialog; try { Structure s1 = new StructureDataType("s1", 0); @@ -197,7 +193,6 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { final Structure myS1Structure = s1Struct; init(myS1Structure, pgmTestCat, false); - program.addListener(restoreListener); // Change the structure. runSwingLater(() -> { @@ -232,7 +227,6 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { } finally { dialog = null; - program.removeListener(restoreListener); } } @@ -240,7 +234,6 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { // program so it goes away. This should close the edit session. @Test public void testProgramRestoreRemovesEditedDtComp() throws Exception { - RestoreListener restoreListener = new RestoreListener(); Window dialog; try { Structure s1 = new StructureDataType("s1", 0); @@ -268,7 +261,6 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { assertTrue(s2.isEquivalent(myS2Structure)); init(myS2Structure, pgmTestCat, false); - program.addListener(restoreListener); // Change the structure. runSwing(() -> { @@ -303,7 +295,6 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { } finally { dialog = null; - program.removeListener(restoreListener); } } @@ -311,14 +302,12 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { // so it goes away. The editor stays since the structure existed previously, but editor reloads. @Test public void testProgramRestoreRemovesEditedComponentDtYes() throws Exception { - RestoreListener restoreListener = new RestoreListener(); Window dialog; try { Structure myStruct = new StructureDataType("myStruct", 0); myStruct.add(new WordDataType()); init(emptyStructure, pgmTestCat, false); - program.addListener(restoreListener); // Add the data type so that we can undo its add. boolean commit = true; @@ -371,7 +360,6 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { } finally { dialog = null; - program.removeListener(restoreListener); } } @@ -379,14 +367,12 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { // so it goes away. The editor stays since the structure existed previously, but doesn't reload. @Test public void testProgramRestoreRemovesEditedComponentDtNo() throws Exception { - RestoreListener restoreListener = new RestoreListener(); Window dialog; try { Structure myStruct = new StructureDataType("myStruct", 0); myStruct.add(new WordDataType()); init(emptyStructure, pgmTestCat, false); - program.addListener(restoreListener); // Add the data type so that we can undo its add. boolean commit = true; @@ -438,45 +424,37 @@ public class StructureEditorProviderTest extends AbstractStructureEditorTest { } finally { dialog = null; - program.removeListener(restoreListener); } } // Test Undo / Redo of program. @Test public void testUnModifiedDtAndProgramRestored() throws Exception { - RestoreListener restoreListener = new RestoreListener(); - try { - init(complexStructure, pgmTestCat, false); - program.addListener(restoreListener); + init(complexStructure, pgmTestCat, false); - // Change the structure - runSwingLater(() -> { - getTable().requestFocus(); - setSelection(new int[] { 4, 5 }); - deleteAction.actionPerformed(new DefaultActionContext()); - try { - model.add(new WordDataType()); - } - catch (UsrException e) { - Assert.fail(e.getMessage()); - } - }); - waitForSwing(); - assertFalse(complexStructure.isEquivalent(model.viewComposite)); - // Apply the changes - invoke(applyAction); - assertTrue(complexStructure.isEquivalent(model.viewComposite)); - // Undo the apply - undo(program); - assertTrue(complexStructure.isEquivalent(model.viewComposite)); - // Redo the apply - redo(program); - assertTrue(complexStructure.isEquivalent(model.viewComposite)); - } - finally { - program.removeListener(restoreListener); - } + // Change the structure + runSwingLater(() -> { + getTable().requestFocus(); + setSelection(new int[] { 4, 5 }); + deleteAction.actionPerformed(new DefaultActionContext()); + try { + model.add(new WordDataType()); + } + catch (UsrException e) { + Assert.fail(e.getMessage()); + } + }); + waitForSwing(); + assertFalse(complexStructure.isEquivalent(model.viewComposite)); + // Apply the changes + invoke(applyAction); + assertTrue(complexStructure.isEquivalent(model.viewComposite)); + // Undo the apply + undo(program); + assertTrue(complexStructure.isEquivalent(model.viewComposite)); + // Redo the apply + redo(program); + assertTrue(complexStructure.isEquivalent(model.viewComposite)); } @Test diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorUnlockedActions5Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorUnlockedActions5Test.java index 242170cc3a..e8c50b06e2 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorUnlockedActions5Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorUnlockedActions5Test.java @@ -31,8 +31,7 @@ import ghidra.program.model.data.*; import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.UsrException; -public class StructureEditorUnlockedActions5Test - extends AbstractStructureEditorTest { +public class StructureEditorUnlockedActions5Test extends AbstractStructureEditorTest { @Test public void testApplyDuplicateName() throws Exception { @@ -614,14 +613,14 @@ public class StructureEditorUnlockedActions5Test undo(program, false); program.flushEvents(); waitForSwing(); - runSwing(() -> provider.domainObjectRestored(program), true); + runSwing(() -> provider.dataTypeManagerRestored(), true); waitForSwing(); assertEquals("myStruct", model.getCompositeName()); redo(program, false); program.flushEvents(); waitForSwing(); - runSwing(() -> provider.domainObjectRestored(program), true); + runSwing(() -> provider.dataTypeManagerRestored(), true); waitForSwing(); assertEquals("myStruct2", model.getCompositeName()); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorProviderTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorProviderTest.java index 9b5e626a0e..2743888c6d 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorProviderTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorProviderTest.java @@ -83,11 +83,9 @@ public class UnionEditorProviderTest extends AbstractUnionEditorTest { // Test Undo / Redo of program. @Test public void testModifiedDtAndProgramRestored() throws Exception { - RestoreListener restoreListener = new RestoreListener(); Window dialog; try { init(complexUnion, pgmTestCat, false); - program.addListener(restoreListener); // Change the union. Swing.runLater(() -> { @@ -138,47 +136,39 @@ public class UnionEditorProviderTest extends AbstractUnionEditorTest { } finally { dialog = null; - program.removeListener(restoreListener); } } // Test Undo / Redo of program. @Test public void testUnModifiedDtAndProgramRestored() throws Exception { - RestoreListener restoreListener = new RestoreListener(); - try { - init(complexUnion, pgmTestCat, false); - program.addListener(restoreListener); + init(complexUnion, pgmTestCat, false); - // Change the union. - Swing.runLater(() -> { - delete(4, 5); - try { - model.add(new WordDataType()); - } - catch (UsrException e) { - Assert.fail(e.getMessage()); - } - }); + // Change the union. + Swing.runLater(() -> { + delete(4, 5); + try { + model.add(new WordDataType()); + } + catch (UsrException e) { + Assert.fail(e.getMessage()); + } + }); - waitForTasks(); - assertFalse(complexUnion.isEquivalent(model.viewComposite)); + waitForTasks(); + assertFalse(complexUnion.isEquivalent(model.viewComposite)); - // Apply the changes - invoke(applyAction); - assertTrue(complexUnion.isEquivalent(model.viewComposite)); + // Apply the changes + invoke(applyAction); + assertTrue(complexUnion.isEquivalent(model.viewComposite)); - // Undo the apply - undo(program); - assertTrue(complexUnion.isEquivalent(model.viewComposite)); + // Undo the apply + undo(program); + assertTrue(complexUnion.isEquivalent(model.viewComposite)); - // Redo the apply - redo(program); - assertTrue(complexUnion.isEquivalent(model.viewComposite)); - } - finally { - program.removeListener(restoreListener); - } + // Redo the apply + redo(program); + assertTrue(complexUnion.isEquivalent(model.viewComposite)); } @Test diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/AbstractEnumEditorUndoRedoTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/AbstractEnumEditorUndoRedoTest.java new file mode 100644 index 0000000000..92d2e1ac08 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/AbstractEnumEditorUndoRedoTest.java @@ -0,0 +1,345 @@ +/* ### + * 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.datamgr.editor; + +import static org.junit.Assert.*; + +import java.awt.*; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicReference; + +import javax.swing.*; + +import org.junit.*; + +import docking.DefaultActionContext; +import docking.action.DockingActionIf; +import docking.widgets.OptionDialog; +import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.*; +import ghidra.program.model.data.Enum; +import ghidra.program.model.listing.Program; +import ghidra.test.*; + +/** + * {@link AbstractEnumEditorUndoRedoTest} contains tests which should be applied to the various + * {@link DataTypeManager} implementations which are responsible for setting {@code dtm} during + * the setUp phase. + */ +public abstract class AbstractEnumEditorUndoRedoTest extends AbstractGhidraHeadedIntegrationTest { + + protected Program program; + protected DataTypeManagerPlugin plugin; + protected PluginTool tool; + protected TestEnv env; + + protected DataTypeManager dtm; // must be set by test implementation during setUp + + @Before + public void setUp() throws Exception { + + ToyProgramBuilder builder = new ToyProgramBuilder("notepad", true); + builder.addCategory(new CategoryPath(CategoryPath.ROOT, "Category1")); + program = builder.getProgram(); + + env = new TestEnv(); + tool = env.showTool(program); + tool.addPlugin(DataTypeManagerPlugin.class.getName()); + plugin = getPlugin(tool, DataTypeManagerPlugin.class); + } + + @After + public void tearDown() throws Exception { + env.dispose(); + } + + @Test + public void testUndoRedo() throws Exception { + + Enum enumDt = editSampleEnum(); + + EnumEditorPanel panel = findEditorPanel(tool.getToolFrame()); + JTable table = panel.getTable(); + EnumTableModel model = (EnumTableModel) table.getModel(); + + // delete a row + table.setRowSelectionInterval(0, 0); + runSwing(() -> { + DockingActionIf action = getDeleteAction(); + action.actionPerformed(new DefaultActionContext()); + }); + applyChanges(true); + assertNull(enumDt.getName(0)); + + // undo + undo(true); + assertEquals("Red", model.getValueAt(0, EnumTableModel.NAME_COL)); + + //redo + redo(true); + assertEquals("Pink", model.getValueAt(0, EnumTableModel.NAME_COL)); + } + + @Test + public void testUndoRemoval() throws Exception { + + editSampleEnum(); + + EnumEditorPanel panel = findEditorPanel(tool.getToolFrame()); + JTable table = panel.getTable(); + EnumTableModel model = (EnumTableModel) table.getModel(); + + assertFalse(model.hasChanges()); + + undo(true); // will remove enum from DTM + + DataType dt = dtm.getDataType("/Category1/Colors"); + assertNull(dt); + + OptionDialog d = waitForDialogComponent(OptionDialog.class); + assertNotNull(d); + assertEquals("Close Enum Editor?", d.getTitle()); + + JButton button = findButtonByText(d.getComponent(), "Continue Edit"); + assertNotNull(button); + runSwing(() -> button.getActionListeners()[0].actionPerformed(null)); + waitForSwing(); + + assertTrue(panel.needsSave()); + + DockingActionIf applyAction = getApplyAction(); + assertTrue(applyAction.isEnabled()); + + applyChanges(true); + + dt = dtm.getDataType("/Category1/Colors"); + assertNotNull(dt); + } + + @Test + public void testChangesBeforeUndoYes() throws Exception { + + editSampleEnum(); + + EnumEditorPanel panel = findEditorPanel(tool.getToolFrame()); + JTable table = panel.getTable(); + EnumTableModel model = (EnumTableModel) table.getModel(); + + int origRowCount = model.getRowCount(); + runSwing(() -> { + DockingActionIf action = getAddAction(); + action.actionPerformed(new DefaultActionContext()); + action.actionPerformed(new DefaultActionContext()); + }); + waitForSwing(); + applyChanges(true); + // make more changes + runSwing(() -> { + DockingActionIf action = getAddAction(); + action.actionPerformed(new DefaultActionContext()); + action.actionPerformed(new DefaultActionContext()); + }); + waitForSwing(); + undo(false); + OptionDialog d = waitForDialogComponent(OptionDialog.class); + assertNotNull(d); + // yes to reload the enum data type + JButton button = findButtonByText(d.getComponent(), "Yes"); + assertNotNull(button); + runSwing(() -> button.getActionListeners()[0].actionPerformed(null)); + waitForSwing(); + assertEquals(origRowCount, model.getRowCount()); + } + + @Test + public void testChangesBeforeUndoNo() throws Exception { + + editSampleEnum(); + + EnumEditorPanel panel = findEditorPanel(tool.getToolFrame()); + JTable table = panel.getTable(); + EnumTableModel model = (EnumTableModel) table.getModel(); + + runSwing(() -> { + int lastRow = model.getRowCount() - 1; + if (lastRow >= 0) { + table.addRowSelectionInterval(lastRow, lastRow); + } + DockingActionIf action = getAddAction(); + action.actionPerformed(new DefaultActionContext()); + action.actionPerformed(new DefaultActionContext()); + }); + waitForSwing(); + applyChanges(true); + // make more changes + runSwing(() -> { + int lastRow = model.getRowCount() - 1; + if (lastRow >= 0) { + table.addRowSelectionInterval(lastRow, lastRow); + } + DockingActionIf action = getAddAction(); + action.actionPerformed(new DefaultActionContext()); + action.actionPerformed(new DefaultActionContext()); + }); + waitForSwing(); + int rowCount = model.getRowCount(); + undo(false); + OptionDialog d = waitForDialogComponent(OptionDialog.class); + assertNotNull(d); + // not to not reload the enum data type + JButton button = findButtonByText(d.getComponent(), "No"); + assertNotNull(button); + runSwing(() -> button.getActionListeners()[0].actionPerformed(null)); + waitForSwing(); + assertEquals(rowCount, model.getRowCount()); + } + +//================================================================================================== +// Private Methods +//================================================================================================== + + private EnumEditorPanel findEditorPanel(Window w) { + Window[] windows = w.getOwnedWindows(); + for (Window window : windows) { + if (window.isVisible() && JDialog.class.isAssignableFrom(window.getClass())) { + Container c = + findContainer(((JDialog) window).getContentPane(), EnumEditorPanel.class); + if (c != null) { + return (EnumEditorPanel) c; + } + } + } + return null; + } + + private Container findContainer(Container parent, Class theClass) { + Component[] c = parent.getComponents(); + for (Component element : c) { + if (theClass.isAssignableFrom(element.getClass())) { + return (Container) element; + } + if (element instanceof Container) { + Container container = findContainer((Container) element, theClass); + if (container != null) { + return container; + } + } + } + return null; + } + + private void applyChanges(boolean doWait) throws Exception { + + DockingActionIf applyAction = getApplyAction(); + assertTrue(applyAction.isEnabled()); + Runnable r = () -> applyAction.actionPerformed(new DefaultActionContext()); + if (doWait) { + runSwing(r); + dtm.flushEvents(); + } + else { + runSwingLater(r); + } + waitForSwing(); + + } + + private DockingActionIf getAddAction() { + return getAction(plugin, "Add Enum Value"); + } + + private DockingActionIf getApplyAction() { + return getAction(plugin, "Apply Enum Changes"); + } + + private DockingActionIf getDeleteAction() { + return getAction(plugin, "Delete Enum Value"); + } + + private Enum editSampleEnum() { + + AtomicReference enumRef = new AtomicReference<>(); + + dtm.withTransaction("Create Test Enum", () -> { + + Category cat = dtm.createCategory(new CategoryPath(CategoryPath.ROOT, "Category1")); + + Enum enumm = new EnumDataType("Colors", 1); + enumm.add("Red", 0); + enumm.add("Green", 0x10); + enumm.add("Blue", 0x20); + enumm.add("Purple", 5); + enumm.add("Turquoise", 0x22); + enumm.add("Pink", 2); + enumm.setDescription("This is a set of Colors"); + + Enum enumDt = (Enum) cat.addDataType(enumm, DataTypeConflictHandler.DEFAULT_HANDLER); + enumRef.set(enumDt); + + dtm.flushEvents(); + waitForSwing(); + + runSwingLater(() -> plugin.edit(enumDt)); + }); + + waitForSwing(); + return enumRef.get(); + + } + + private void undo(boolean doWait) throws Exception { + Runnable r = () -> { + try { + undo(); + dtm.flushEvents(); + } + catch (Exception e) { + Assert.fail(e.getMessage()); + } + }; + if (doWait) { + runSwing(r); + } + else { + runSwingLater(r); + } + waitForSwing(); + } + + private void redo(boolean doWait) throws Exception { + Runnable r = () -> { + try { + redo(); + dtm.flushEvents(); + } + catch (Exception e) { + Assert.fail(e.getMessage()); + } + }; + if (doWait) { + runSwing(r); + } + else { + runSwingLater(r); + } + waitForSwing(); + } + + abstract void undo() throws IOException; + + abstract void redo() throws IOException; +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/EnumEditor2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/EnumEditor2Test.java index 81c41f4187..dbda274b50 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/EnumEditor2Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/EnumEditor2Test.java @@ -668,110 +668,6 @@ public class EnumEditor2Test extends AbstractGhidraHeadedIntegrationTest { } - @Test - public void testUndoRedo() throws Exception { - - Enum enumDt = editSampleEnum(); - - EnumEditorPanel panel = findEditorPanel(tool.getToolFrame()); - JTable table = panel.getTable(); - EnumTableModel model = (EnumTableModel) table.getModel(); - - // delete a row - table.setRowSelectionInterval(0, 0); - runSwing(() -> { - DockingActionIf action = getDeleteAction(); - action.actionPerformed(new DefaultActionContext()); - }); - applyChanges(true); - assertNull(enumDt.getName(0)); - // undo - undo(program); - assertEquals("Red", model.getValueAt(0, EnumTableModel.NAME_COL)); - - //redo - redo(program); - assertEquals("Pink", model.getValueAt(0, EnumTableModel.NAME_COL)); - } - - @Test - public void testChangesBeforeUndoYes() throws Exception { - - editSampleEnum(); - - EnumEditorPanel panel = findEditorPanel(tool.getToolFrame()); - JTable table = panel.getTable(); - EnumTableModel model = (EnumTableModel) table.getModel(); - - int origRowCount = model.getRowCount(); - runSwing(() -> { - DockingActionIf action = getAddAction(); - action.actionPerformed(new DefaultActionContext()); - action.actionPerformed(new DefaultActionContext()); - }); - waitForSwing(); - applyChanges(true); - // make more changes - runSwing(() -> { - DockingActionIf action = getAddAction(); - action.actionPerformed(new DefaultActionContext()); - action.actionPerformed(new DefaultActionContext()); - }); - waitForSwing(); - undo(false); - OptionDialog d = waitForDialogComponent(OptionDialog.class); - assertNotNull(d); - // yes to reload the enum data type - JButton button = findButtonByText(d.getComponent(), "Yes"); - assertNotNull(button); - runSwing(() -> button.getActionListeners()[0].actionPerformed(null)); - waitForSwing(); - assertEquals(origRowCount, model.getRowCount()); - } - - @Test - public void testChangesBeforeUndoNo() throws Exception { - - editSampleEnum(); - - EnumEditorPanel panel = findEditorPanel(tool.getToolFrame()); - JTable table = panel.getTable(); - EnumTableModel model = (EnumTableModel) table.getModel(); - - runSwing(() -> { - int lastRow = model.getRowCount() - 1; - if (lastRow >= 0) { - table.addRowSelectionInterval(lastRow, lastRow); - } - DockingActionIf action = getAddAction(); - action.actionPerformed(new DefaultActionContext()); - action.actionPerformed(new DefaultActionContext()); - }); - waitForSwing(); - applyChanges(true); - // make more changes - runSwing(() -> { - int lastRow = model.getRowCount() - 1; - if (lastRow >= 0) { - table.addRowSelectionInterval(lastRow, lastRow); - } - DockingActionIf action = getAddAction(); - action.actionPerformed(new DefaultActionContext()); - action.actionPerformed(new DefaultActionContext()); - }); - waitForSwing(); - int rowCount = model.getRowCount(); - undo(false); - OptionDialog d = waitForDialogComponent(OptionDialog.class); - assertNotNull(d); - // not to not reload the enum data type - JButton button = findButtonByText(d.getComponent(), "No"); - assertNotNull(button); - runSwing(() -> button.getActionListeners()[0].actionPerformed(null)); - waitForSwing(); - assertEquals(rowCount, model.getRowCount()); - } - //================================================================================================== // Private Methods //================================================================================================== @@ -936,23 +832,4 @@ public class EnumEditor2Test extends AbstractGhidraHeadedIntegrationTest { return enumDt; } - private void undo(boolean doWait) throws Exception { - Runnable r = () -> { - try { - program.undo(); - program.flushEvents(); - } - catch (Exception e) { - Assert.fail(e.getMessage()); - } - }; - if (doWait) { - runSwing(r); - } - else { - runSwingLater(r); - } - waitForSwing(); - } - } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/FileArchiveEnumEditorUndoRedoTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/FileArchiveEnumEditorUndoRedoTest.java new file mode 100644 index 0000000000..b67b0d46bc --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/FileArchiveEnumEditorUndoRedoTest.java @@ -0,0 +1,71 @@ +/* ### + * 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.datamgr.editor; + +import static org.junit.Assert.*; + +import java.io.File; +import java.io.IOException; + +import org.junit.After; +import org.junit.Before; + +import ghidra.app.plugin.core.datamgr.archive.Archive; +import ghidra.program.model.data.FileDataTypeManager; + +public class FileArchiveEnumEditorUndoRedoTest extends AbstractEnumEditorUndoRedoTest { + + private File tempGdt; + private Archive fileArchive; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + tempGdt = createTempFileForTest(".gdt"); + tempGdt.delete(); + + fileArchive = plugin.getDataTypeManagerHandler().createArchive(tempGdt); + + assertTrue(fileArchive.isModifiable()); + + dtm = fileArchive.getDataTypeManager(); + } + + @After + @Override + public void tearDown() throws Exception { + if (fileArchive != null) { + plugin.getDataTypeManagerHandler().closeArchive(fileArchive); + tempGdt.delete(); + } + super.tearDown(); + } + + @Override + void undo() throws IOException { + FileDataTypeManager fileDtm = (FileDataTypeManager) fileArchive.getDataTypeManager(); + fileDtm.undo(); + } + + @Override + void redo() throws IOException { + FileDataTypeManager fileDtm = (FileDataTypeManager) fileArchive.getDataTypeManager(); + fileDtm.redo(); + } + +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/ProgramEnumEditorUndoRedoTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/ProgramEnumEditorUndoRedoTest.java new file mode 100644 index 0000000000..2ff7e71306 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/ProgramEnumEditorUndoRedoTest.java @@ -0,0 +1,41 @@ +/* ### + * 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.datamgr.editor; + +import java.io.IOException; + +import org.junit.Before; + +public class ProgramEnumEditorUndoRedoTest extends AbstractEnumEditorUndoRedoTest { + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + dtm = program.getDataTypeManager(); + } + + @Override + void undo() throws IOException { + program.undo(); + } + + @Override + void redo() throws IOException { + program.redo(); + } + +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/ProjectArchiveEnumEditorUndoRedoTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/ProjectArchiveEnumEditorUndoRedoTest.java new file mode 100644 index 0000000000..6703373f87 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/editor/ProjectArchiveEnumEditorUndoRedoTest.java @@ -0,0 +1,69 @@ +/* ### + * 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.datamgr.editor; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.junit.After; +import org.junit.Before; + +import ghidra.app.plugin.core.datamgr.archive.Archive; +import ghidra.framework.model.DomainFolder; +import ghidra.program.database.DataTypeArchiveDB; + +public class ProjectArchiveEnumEditorUndoRedoTest extends AbstractEnumEditorUndoRedoTest { + + Archive projectArchive; + DataTypeArchiveDB dataTypeArchiveDB; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + DomainFolder rootFolder = tool.getProject().getProjectData().getRootFolder(); + + dataTypeArchiveDB = new DataTypeArchiveDB(rootFolder, "Test", tool); + + projectArchive = plugin.getDataTypeManagerHandler().openArchive(dataTypeArchiveDB); + + assertTrue(projectArchive.isModifiable()); + + dtm = dataTypeArchiveDB.getDataTypeManager(); + } + + @After + @Override + public void tearDown() throws Exception { + if (projectArchive != null) { + plugin.getDataTypeManagerHandler().closeArchive(projectArchive); + } + super.tearDown(); + } + + @Override + void undo() throws IOException { + dataTypeArchiveDB.undo(); + } + + @Override + void redo() throws IOException { + dataTypeArchiveDB.redo(); + } + +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/PositiveStackEditorProviderTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/PositiveStackEditorProviderTest.java index 499ee3dc70..5dac4c5455 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/PositiveStackEditorProviderTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/PositiveStackEditorProviderTest.java @@ -22,8 +22,6 @@ import javax.swing.JTextField; import org.junit.Before; import org.junit.Test; -import ghidra.framework.model.*; -import ghidra.program.model.data.DataTypeManagerDomainObject; import ghidra.program.model.data.Pointer; public class PositiveStackEditorProviderTest extends AbstractStackEditorTest { @@ -113,39 +111,4 @@ public class PositiveStackEditorProviderTest extends AbstractStackEditorTest { assertEquals(0x4, stackModel.getParameterSize()); } -// public void testIncreasePosReturnAddrOffset() throws Exception { -// init(SIMPLE_STACK); -// assertEquals(0x20, stackModel.getFrameSize()); -// assertEquals(0x0, stackModel.getReturnAddressOffset()); -// assertEquals(0x12, stackModel.getLocalSize()); -// assertEquals(-0x8, stackModel.getParameterOffset()); -// assertEquals(0x7, stackModel.getParameterSize()); -// } -// -// public void testDecreasePosReturnAddrOffset() throws Exception { -// init(SIMPLE_STACK); -// assertEquals(0x20, stackModel.getFrameSize()); -// assertEquals(0x0, stackModel.getReturnAddressOffset()); -// assertEquals(0x12, stackModel.getLocalSize()); -// assertEquals(-0x8, stackModel.getParameterOffset()); -// assertEquals(0x7, stackModel.getParameterSize()); -// } - - protected class RestoreListener implements DomainObjectListener { - /** - * @see ghidra.framework.model.DomainObjectListener#domainObjectChanged(ghidra.framework.model.DomainObjectChangedEvent) - */ - @Override - public void domainObjectChanged(DomainObjectChangedEvent event) { - if (event.contains(DomainObjectEvent.RESTORED)) { - Object source = event.getSource(); - if (source instanceof DataTypeManagerDomainObject) { - DataTypeManagerDomainObject restoredDomainObject = - (DataTypeManagerDomainObject) source; - provider.domainObjectRestored(restoredDomainObject); - } - } - } - } - } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/datatype/DataTypeSelectionDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/datatype/DataTypeSelectionDialogTest.java index 79e401f6cc..eab365ddce 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/datatype/DataTypeSelectionDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/util/datatype/DataTypeSelectionDialogTest.java @@ -1119,6 +1119,11 @@ public class DataTypeSelectionDialogTest extends AbstractGhidraHeadedIntegration public void programArchitectureChanged(DataTypeManager dataTypeManager) { // don't care for now } + + @Override + public void restored(DataTypeManager dataTypeManager) { + // don't care for now + } } private class CustomDataType extends StructureDataType { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/CategoryTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/CategoryTest.java index ba5677b871..fa94f5b6cd 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/CategoryTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/CategoryTest.java @@ -910,5 +910,10 @@ public class CategoryTest extends AbstractGhidraHeadedIntegrationTest { public void programArchitectureChanged(DataTypeManager dataTypeManager) { // don't care } + + @Override + public void restored(DataTypeManager dataTypeManager) { + // don't care + } } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java index d2569cc080..87500db62a 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectAdapterDB.java @@ -476,13 +476,21 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter implemen @Override public void invalidate() { clearCache(false); - super.invalidate(); + super.invalidate(); // fires RESTORED event } protected void clearCache(boolean all) { options.clearCache(); } + /** + * Indicates that this domain object has been restored to a completely different state due + * to a transaction undo/redo/rollback or a database merge operation. + */ + protected void domainObjectRestored() { + invalidate(); + } + @Override public synchronized boolean canSave() { DomainFile df = getDomainFile(); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectTransactionManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectTransactionManager.java index 956bd37534..d9791b17d2 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectTransactionManager.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DomainObjectTransactionManager.java @@ -89,12 +89,10 @@ class DomainObjectTransactionManager extends AbstractTransactionManager { if (domainObj.changeSet != null) { domainObj.changeSet.endTransaction(!rollback); } - domainObj.clearCache(false); - } - - domainObj.fireEvent(new DomainObjectChangeRecord(DomainObjectEvent.RESTORED)); - if (notify) { - notifyEndTransaction(); + domainObj.domainObjectRestored(); + if (notify) { + notifyEndTransaction(); + } } } @@ -178,13 +176,12 @@ class DomainObjectTransactionManager extends AbstractTransactionManager { domainObj.changeSet.endTransaction(false); } } - domainObj.clearCache(false); + domainObj.domainObjectRestored(); + transaction.restoreToolStates(true); + transaction = null; if (notify) { notifyEndTransaction(); } - domainObj.fireEvent(new DomainObjectChangeRecord(DomainObjectEvent.RESTORED)); - transaction.restoreToolStates(true); - transaction = null; } } catch (IOException e) { @@ -272,8 +269,8 @@ class DomainObjectTransactionManager extends AbstractTransactionManager { if (domainObj.changeSet != null) { domainObj.changeSet.redo(); } - domainObj.fireEvent(new DomainObjectChangeRecord(DomainObjectEvent.RESTORED)); undoList.addLast(t); + domainObj.domainObjectRestored(); t.restoreToolStates(false); if (notify) { notifyUndoRedo(); @@ -290,9 +287,8 @@ class DomainObjectTransactionManager extends AbstractTransactionManager { if (domainObj.changeSet != null) { domainObj.changeSet.undo(); } - domainObj.clearCache(false); - domainObj.fireEvent(new DomainObjectChangeRecord(DomainObjectEvent.RESTORED)); redoList.addLast(t); + domainObj.domainObjectRestored(); t.restoreToolStates(true); if (notify) { notifyUndoRedo(); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java index 120f59174b..7ab51d95e3 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/GhidraFileData.java @@ -1169,7 +1169,7 @@ public class GhidraFileData { projectLocator.isTransient())); folderItem.setCheckout(checkout.getCheckoutId(), exclusive, checkout.getCheckoutVersion(), folderItem.getCurrentVersion()); - + if (inUseDomainObj != null) { // Reset source file and change-sets for open database getContentHandler().resetDBSourceFile(folderItem, inUseDomainObj); @@ -1185,11 +1185,11 @@ public class GhidraFileData { // Ignore - should result in Hijacked file } } - + } // end of synchronized block if (inUseDomainObj != null) { - inUseDomainObj.invalidate(); + inUseDomainObj.domainObjectRestored(); } } finally { @@ -1537,7 +1537,7 @@ public class GhidraFileData { } } } - + if (inUseDomainObj != null) { // Reset source file and change-sets for open database contentHandler.resetDBSourceFile(folderItem, inUseDomainObj); @@ -1550,7 +1550,7 @@ public class GhidraFileData { } // end of synchronized block if (inUseDomainObj != null) { - inUseDomainObj.invalidate(); + inUseDomainObj.domainObjectRestored(); } } finally { @@ -1919,7 +1919,7 @@ public class GhidraFileData { inUseDomainObj = getAndLockInUseDomainObjectForMergeUpdate("merge"); ContentHandler contentHandler = getContentHandler(); - + if (!modifiedSinceCheckout()) { // Quick merge folderItem.updateCheckout(versionedFolderItem, true, monitor); @@ -1999,13 +1999,13 @@ public class GhidraFileData { ClientUtil.getUserName()); tmpItem = null; } - + Msg.info(this, "Updated checkout completed for " + name); if (inUseDomainObj != null) { // Reset source file and change-sets for open database contentHandler.resetDBSourceFile(folderItem, inUseDomainObj); - inUseDomainObj.invalidate(); + inUseDomainObj.domainObjectRestored(); } } finally { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DataTypeArchiveDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DataTypeArchiveDB.java index 029695ad0a..a3ba467476 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DataTypeArchiveDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DataTypeArchiveDB.java @@ -15,6 +15,7 @@ */ package ghidra.program.database; +import java.io.File; import java.io.IOException; import java.util.*; @@ -22,7 +23,8 @@ import db.*; import ghidra.framework.Application; import ghidra.framework.data.DomainObjectAdapterDB; import ghidra.framework.data.OpenMode; -import ghidra.framework.model.*; +import ghidra.framework.model.DomainFile; +import ghidra.framework.model.DomainFolder; import ghidra.framework.options.Options; import ghidra.program.model.data.*; import ghidra.program.model.listing.DataTypeArchive; @@ -515,12 +517,6 @@ public class DataTypeArchiveDB extends DomainObjectAdapterDB implements DataType } } - @Override - public void invalidate() { - clearCache(false); - fireEvent(new DomainObjectChangeRecord(DomainObjectEvent.RESTORED)); - } - @Override public boolean isChangeable() { return changeable; @@ -535,6 +531,27 @@ public class DataTypeArchiveDB extends DomainObjectAdapterDB implements DataType this.changeSet = changeSet; } + @Override + public void save(String comment, TaskMonitor monitor) throws IOException, CancelledException { + try { + super.save(comment, monitor); + } + finally { + dataTypeManager.clearUndo(); + } + } + + @Override + public void saveToPackedFile(File outputFile, TaskMonitor monitor) + throws IOException, CancelledException { + try { + super.saveToPackedFile(outputFile, monitor); + } + finally { + dataTypeManager.clearUndo(); + } + } + @Override public Map getMetadata() { @@ -568,4 +585,10 @@ public class DataTypeArchiveDB extends DomainObjectAdapterDB implements DataType public void updateID() { dataTypeManager.updateID(); } + + @Override + protected void domainObjectRestored() { + super.domainObjectRestored(); + dataTypeManager.notifyRestored(); + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java index 371c4e0762..9503e1c095 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java @@ -2495,4 +2495,10 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM } ((ProgramCompilerSpec) compilerSpec).registerProgramOptions(); } + + @Override + protected void domainObjectRestored() { + super.domainObjectRestored(); + getDataTypeManager().notifyRestored(); + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProjectDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProjectDataTypeManager.java index 27fb37f93a..687e49e5bc 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProjectDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProjectDataTypeManager.java @@ -16,8 +16,7 @@ package ghidra.program.database; import java.io.IOException; -import java.util.Map; -import java.util.Set; +import java.util.*; import javax.help.UnsupportedOperationException; @@ -196,6 +195,11 @@ public class ProjectDataTypeManager extends StandAloneDataTypeManager // do nothing } + @Override + protected void initTransactionState() { + // do nothing - rely on DataTypeArchiveDB + } + @Override public Transaction openTransaction(String description) throws IllegalStateException { return dataTypeArchive.openTransaction(description); @@ -208,14 +212,75 @@ public class ProjectDataTypeManager extends StandAloneDataTypeManager } @Override - public void flushEvents() { - dataTypeArchive.flushEvents(); + public void endTransaction(int transactionID, boolean commit) { + dataTypeArchive.endTransaction(transactionID, commit); + } + + @Override + public void undo() { + try { + dataTypeArchive.undo(); + } + catch (IOException e) { + dbError(e); + } + } + + @Override + public void redo() { + try { + dataTypeArchive.redo(); + } + catch (IOException e) { + dbError(e); + } } @SuppressWarnings("sync-override") @Override - public void endTransaction(int transactionID, boolean commit) { - dataTypeArchive.endTransaction(transactionID, commit); + public void clearUndo() { + dataTypeArchive.clearUndo(); + } + + @SuppressWarnings("sync-override") + @Override + public boolean canRedo() { + return dataTypeArchive.canRedo(); + } + + @SuppressWarnings("sync-override") + @Override + public boolean canUndo() { + return dataTypeArchive.canUndo(); + } + + @SuppressWarnings("sync-override") + @Override + public String getRedoName() { + return dataTypeArchive.getRedoName(); + } + + @SuppressWarnings("sync-override") + @Override + public String getUndoName() { + return dataTypeArchive.getUndoName(); + } + + @SuppressWarnings("sync-override") + @Override + public List getAllUndoNames() { + return dataTypeArchive.getAllUndoNames(); + } + + @SuppressWarnings("sync-override") + @Override + public List getAllRedoNames() { + return dataTypeArchive.getAllRedoNames(); + } + + @Override + public void flushEvents() { + dataTypeArchive.flushEvents(); } @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 9dccb37130..0ff20c9cef 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 @@ -230,7 +230,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { try { dbHandle = new DBHandle(); readOnlyMode = false; - int id = startTransaction(""); + long txId = dbHandle.startTransaction(); try { init(OpenMode.CREATE, TaskMonitor.DUMMY); } @@ -238,7 +238,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { throw new AssertException(e); // unexpected } finally { - endTransaction(id, true); + dbHandle.endTransaction(txId, true); } } catch (IOException e) { @@ -319,7 +319,8 @@ abstract public class DataTypeManagerDB implements DataTypeManager { private void initPackedDatabase(ResourceFile packedDBfile, OpenMode openMode, TaskMonitor monitor) throws CancelledException, IOException { - try (Transaction tx = openTransaction("")) { + long txId = dbHandle.startTransaction(); + try { init(openMode, monitor); if (openMode != OpenMode.CREATE && hasDataOrganizationChange(true)) { @@ -343,6 +344,9 @@ abstract public class DataTypeManagerDB implements DataTypeManager { throw new IOException(e); } } + finally { + dbHandle.endTransaction(txId, true); + } } /** @@ -951,6 +955,15 @@ abstract public class DataTypeManagerDB implements DataTypeManager { return dbHandle.isTransactionActive(); } + /** + * This method should be invoked following an undo/redo or a transaction rollback situation. + * This will notify {@link DataTypeManagerChangeListenerHandler} and its listeners that this + * manager has just been restored (e.g., undo/redo/rollback). + */ + public void notifyRestored() { + defaultListener.restored(this); + } + abstract protected String getDomainFileID(); abstract protected String getPath(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltInDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltInDataTypeManager.java index e17afd1941..1defcd080e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltInDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltInDataTypeManager.java @@ -99,6 +99,16 @@ public final class BuiltInDataTypeManager extends StandAloneDataTypeManager { super.endTransaction(transactionID, commit); } + @Override + public synchronized boolean canUndo() { + return false; + } + + @Override + public synchronized boolean canRedo() { + return false; + } + @Override public Category createCategory(CategoryPath path) { if (path != CategoryPath.ROOT) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManager.java index 2069aeff0c..395ece69da 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManager.java @@ -27,6 +27,8 @@ import ghidra.util.InvalidNameException; import ghidra.util.UniversalID; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; +import utility.function.ExceptionalCallback; +import utility.function.ExceptionalSupplier; /** * Interface for Managing data types. @@ -384,6 +386,72 @@ public interface DataTypeManager { */ public void endTransaction(int transactionID, boolean commit); + /** + * Performs the given callback inside of a transaction. Use this method in place of the more + * verbose try/catch/finally semantics. + *

+ *

+	 * program.withTransaction("My Description", () -> {
+	 * 	// ... Do something
+	 * });
+	 * 
+ * + *

+ * Note: the transaction created by this method will always be committed when the call is + * finished. If you need the ability to abort transactions, then you need to use the other + * methods on this interface. + * + * @param description brief description of transaction + * @param callback the callback that will be called inside of a transaction + * @throws E any exception that may be thrown in the given callback + */ + public default void withTransaction(String description, + ExceptionalCallback callback) throws E { + int id = startTransaction(description); + try { + callback.call(); + } + finally { + endTransaction(id, true); + } + } + + /** + * Calls the given supplier inside of a transaction. Use this method in place of the more + * verbose try/catch/finally semantics. + *

+ *

+	 * program.withTransaction("My Description", () -> {
+	 * 	// ... Do something
+	 * 	return result;
+	 * });
+	 * 
+ *

+ * If you do not need to supply a result, then use + * {@link #withTransaction(String, ExceptionalCallback)} instead. + * + * @param the exception that may be thrown from this method + * @param the type of result returned by the supplier + * @param description brief description of transaction + * @param supplier the supplier that will be called inside of a transaction + * @return the result returned by the supplier + * @throws E any exception that may be thrown in the given callback + */ + public default T withTransaction(String description, + ExceptionalSupplier supplier) throws E { + T t = null; + boolean success = false; + int id = startTransaction(description); + try { + t = supplier.get(); + success = true; + } + finally { + endTransaction(id, success); + } + return t; + } + /** * Force all pending notification events to be flushed * @throws IllegalStateException if the client is holding this object's lock diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManagerChangeListener.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManagerChangeListener.java index c1791265f4..27630678e3 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManagerChangeListener.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManagerChangeListener.java @@ -15,6 +15,8 @@ */ package ghidra.program.model.data; +import ghidra.framework.model.DomainObjectEvent; + /** * The listener interface for notification of changes to a DataTypeManager */ @@ -22,6 +24,7 @@ public interface DataTypeManagerChangeListener { /** * Notification when category is added. + * * @param dtm the dataType manager * @param path the categoryPath of the newly added category. */ @@ -29,6 +32,7 @@ public interface DataTypeManagerChangeListener { /** * Notification when a category is removed. + * * @param dtm data type manager associated with the category * @param path the categoryPath of the category that was removed. */ @@ -36,6 +40,7 @@ public interface DataTypeManagerChangeListener { /** * Notification when category is renamed. + * * @param dtm data type manager associated with the category * @param oldPath the path of the category before it was renamed. * @param newPath the path of the category after it was renamed. This path will only differ in @@ -44,7 +49,8 @@ public interface DataTypeManagerChangeListener { public void categoryRenamed(DataTypeManager dtm, CategoryPath oldPath, CategoryPath newPath); /** - * Notification when a category is reparented to new category. + * Notification when a category is reparented to new category. + * * @param dtm data type manager associated with the category * @param oldPath the path of the category before it was moved. * @param newPath the path of the category after it was moved. @@ -53,6 +59,7 @@ public interface DataTypeManagerChangeListener { /** * Notification when a data type is added to a category + * * @param dtm data type manager for the given category paths. * @param path the DataTypePath of the newly added datatype. */ @@ -60,6 +67,7 @@ public interface DataTypeManagerChangeListener { /** * Notification when data type is removed. + * * @param dtm data type manager for the given category paths. * @param path the DataTypePath of the removed datatype. */ @@ -67,6 +75,7 @@ public interface DataTypeManagerChangeListener { /** * Notification when data type is renamed. + * * @param dtm data type manager for the given category paths. * @param oldPath the path of the datatype before it was renamed. * @param newPath the path of the datatype after it was renamed. @@ -75,6 +84,7 @@ public interface DataTypeManagerChangeListener { /** * Notification when a data type is moved. + * * @param dtm data type manager for the given category paths. * @param oldPath the path of the datatype before it was moved. * @param newPath the path of the datatype after it was moved. @@ -83,6 +93,7 @@ public interface DataTypeManagerChangeListener { /** * Notification when data type is changed. + * * @param dtm data type manager for the given category paths. * @param path the path of the datatype that changed. */ @@ -90,6 +101,7 @@ public interface DataTypeManagerChangeListener { /** * Notification when a data type has been replaced. + * * @param dtm data type manager for the given category paths. * @param oldPath the path of the datatype that was replaced. * @param newPath the path of the datatype that replaced the existing datatype. @@ -100,6 +112,7 @@ public interface DataTypeManagerChangeListener { /** * Notification the favorite status of a datatype has changed + * * @param dtm data type manager for the given category paths. * @param path the DataTypePath of the datatype had its favorite status changed. * @param isFavorite reflects the current favorite status of the datatype. @@ -109,6 +122,7 @@ public interface DataTypeManagerChangeListener { /** * Notification that the information for a particular source archive has changed. Typically, * this would be because it was renamed or moved. + * * @param dataTypeManager data type manager referring to the given source information. * @param sourceArchive the changed data type source information */ @@ -118,6 +132,7 @@ public interface DataTypeManagerChangeListener { /** * Notification that the information for a source archive has been added. This happens when * a data type from the indicated source archive is added to this data type manager. + * * @param dataTypeManager data type manager referring to the given source information. * @param sourceArchive the new data type source information */ @@ -127,7 +142,17 @@ public interface DataTypeManagerChangeListener { /** * Notification that the program architecture associated with the specified * dataTypeManager has changed. + * * @param dataTypeManager data type manager referring to the given source information. */ public void programArchitectureChanged(DataTypeManager dataTypeManager); + + /** + * Notification that the specified datatype manager has been restored to a + * previous state. NOTE: this notification may duplicate the {@link DomainObjectEvent#RESTORED} + * employed by {@link DataTypeManagerDomainObject} cases. + * + * @param dataTypeManager data type manager that has been restored + */ + public void restored(DataTypeManager dataTypeManager); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManagerChangeListenerAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManagerChangeListenerAdapter.java index d55f93374d..7d5d69c27d 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManagerChangeListenerAdapter.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManagerChangeListenerAdapter.java @@ -70,11 +70,15 @@ public class DataTypeManagerChangeListenerAdapter implements DataTypeManagerChan } @Override - public void sourceArchiveChanged(DataTypeManager dataTypeManager, SourceArchive dataTypeSource) { + public void sourceArchiveChanged(DataTypeManager dataTypeManager, + SourceArchive dataTypeSource) { } @Override public void programArchitectureChanged(DataTypeManager dataTypeManager) { } + @Override + public void restored(DataTypeManager dataTypeManager) { + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManagerChangeListenerHandler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManagerChangeListenerHandler.java index f5765860f9..267a8b67a7 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManagerChangeListenerHandler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManagerChangeListenerHandler.java @@ -57,7 +57,7 @@ public class DataTypeManagerChangeListenerHandler implements DataTypeManagerChan if (listenerList.isEmpty()) { return; } - invokeRunnable(() -> { + invokeLater(() -> { for (DataTypeManagerChangeListener listener : listenerList) { listener.categoryAdded(dtm, path); } @@ -65,12 +65,11 @@ public class DataTypeManagerChangeListenerHandler implements DataTypeManagerChan } @Override - public void categoryMoved(DataTypeManager dtm, CategoryPath oldPath, - CategoryPath newPath) { + public void categoryMoved(DataTypeManager dtm, CategoryPath oldPath, CategoryPath newPath) { if (listenerList.isEmpty()) { return; } - invokeRunnable(() -> { + invokeLater(() -> { for (DataTypeManagerChangeListener listener : listenerList) { listener.categoryMoved(dtm, oldPath, newPath); } @@ -82,7 +81,7 @@ public class DataTypeManagerChangeListenerHandler implements DataTypeManagerChan if (listenerList.isEmpty()) { return; } - invokeRunnable(() -> { + invokeLater(() -> { for (DataTypeManagerChangeListener listener : listenerList) { listener.categoryRemoved(dtm, path); } @@ -90,13 +89,12 @@ public class DataTypeManagerChangeListenerHandler implements DataTypeManagerChan } @Override - public void categoryRenamed(DataTypeManager dtm, CategoryPath oldPath, - CategoryPath newPath) { + public void categoryRenamed(DataTypeManager dtm, CategoryPath oldPath, CategoryPath newPath) { if (listenerList.isEmpty()) { return; } - invokeRunnable(() -> { + invokeLater(() -> { for (DataTypeManagerChangeListener listener : listenerList) { listener.categoryRenamed(dtm, oldPath, newPath); } @@ -109,7 +107,7 @@ public class DataTypeManagerChangeListenerHandler implements DataTypeManagerChan if (listenerList.isEmpty()) { return; } - invokeRunnable(() -> { + invokeLater(() -> { for (DataTypeManagerChangeListener listener : listenerList) { listener.dataTypeAdded(dtm, path); } @@ -122,7 +120,7 @@ public class DataTypeManagerChangeListenerHandler implements DataTypeManagerChan if (listenerList.isEmpty()) { return; } - invokeRunnable(() -> { + invokeLater(() -> { for (DataTypeManagerChangeListener listener : listenerList) { listener.dataTypeChanged(dtm, path); } @@ -130,13 +128,12 @@ public class DataTypeManagerChangeListenerHandler implements DataTypeManagerChan } @Override - public void dataTypeMoved(DataTypeManager dtm, DataTypePath oldPath, - DataTypePath newPath) { + public void dataTypeMoved(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath) { if (listenerList.isEmpty()) { return; } - invokeRunnable(() -> { + invokeLater(() -> { for (DataTypeManagerChangeListener listener : listenerList) { listener.dataTypeMoved(dtm, oldPath, newPath); } @@ -149,7 +146,7 @@ public class DataTypeManagerChangeListenerHandler implements DataTypeManagerChan if (listenerList.isEmpty()) { return; } - invokeRunnable(() -> { + invokeLater(() -> { for (DataTypeManagerChangeListener listener : listenerList) { listener.dataTypeRemoved(dtm, path); } @@ -157,13 +154,12 @@ public class DataTypeManagerChangeListenerHandler implements DataTypeManagerChan } @Override - public void dataTypeRenamed(DataTypeManager dtm, DataTypePath oldPath, - DataTypePath newPath) { + public void dataTypeRenamed(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath) { if (listenerList.isEmpty()) { return; } - invokeRunnable(() -> { + invokeLater(() -> { for (DataTypeManagerChangeListener listener : listenerList) { listener.dataTypeRenamed(dtm, oldPath, newPath); listener.favoritesChanged(dtm, oldPath, false); @@ -176,23 +172,20 @@ public class DataTypeManagerChangeListenerHandler implements DataTypeManagerChan listenerList = WeakDataStructureFactory.createCopyOnReadWeakSet(); } - private void invokeRunnable(Runnable r) { -// if (SwingUtilities.isEventDispatchThread()) { -// r.run(); -// } -// else { + private void invokeLater(Runnable r) { + // Since this method may be invoked from within a synchronized block it is important that + // the runnable be executed later in a non-blocking fashion. SwingUtilities.invokeLater(r); -// } } @Override - public void dataTypeReplaced(DataTypeManager dtm, DataTypePath oldPath, - DataTypePath newPath, DataType newDataType) { + public void dataTypeReplaced(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath, + DataType newDataType) { if (listenerList.isEmpty()) { return; } - invokeRunnable(() -> { + invokeLater(() -> { for (DataTypeManagerChangeListener listener : listenerList) { listener.dataTypeReplaced(dtm, oldPath, newPath, newDataType); } @@ -204,7 +197,7 @@ public class DataTypeManagerChangeListenerHandler implements DataTypeManagerChan if (listenerList.isEmpty()) { return; } - invokeRunnable(() -> { + invokeLater(() -> { for (DataTypeManagerChangeListener listener : listenerList) { listener.favoritesChanged(dtm, path, isFavorite); } @@ -218,7 +211,7 @@ public class DataTypeManagerChangeListenerHandler implements DataTypeManagerChan if (listenerList.isEmpty()) { return; } - invokeRunnable(() -> { + invokeLater(() -> { for (DataTypeManagerChangeListener listener : listenerList) { listener.sourceArchiveChanged(dataTypeManager, dataTypeSource); } @@ -226,27 +219,39 @@ public class DataTypeManagerChangeListenerHandler implements DataTypeManagerChan } @Override - public void sourceArchiveAdded(DataTypeManager dataTypeManager, - SourceArchive dataTypeSource) { + public void sourceArchiveAdded(DataTypeManager dataTypeManager, SourceArchive dataTypeSource) { if (listenerList.isEmpty()) { return; } - invokeRunnable(() -> { + invokeLater(() -> { for (DataTypeManagerChangeListener listener : listenerList) { listener.sourceArchiveAdded(dataTypeManager, dataTypeSource); } }); } + @Override public void programArchitectureChanged(DataTypeManager dataTypeManager) { if (listenerList.isEmpty()) { return; } - invokeRunnable(() -> { + invokeLater(() -> { for (DataTypeManagerChangeListener listener : listenerList) { listener.programArchitectureChanged(dataTypeManager); } }); } + + @Override + public void restored(DataTypeManager dtm) { + if (listenerList.isEmpty()) { + return; + } + invokeLater(() -> { + for (DataTypeManagerChangeListener listener : listenerList) { + listener.restored(dtm); + } + }); + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/FileDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/FileDataTypeManager.java index 74739537af..4516b06f5d 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/FileDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/FileDataTypeManager.java @@ -155,8 +155,8 @@ public class FileDataTypeManager extends StandAloneDataTypeManager * match another existing archive database. * @param saveFile the file to save * @param newUniversalId the new id to use - * @throws DuplicateFileException - * @throws IOException + * @throws DuplicateFileException if save file already exists + * @throws IOException if IO error occurs */ public void saveAs(File saveFile, UniversalID newUniversalId) throws DuplicateFileException, IOException { @@ -173,11 +173,16 @@ public class FileDataTypeManager extends StandAloneDataTypeManager catch (CancelledException e) { // Cancel can't happen because we are using a dummy monitor } + finally { + clearUndo(); + } } /** * Saves the data type manager to the given file * @param saveFile the file to save + * @throws DuplicateFileException if save file already exists + * @throws IOException if IO error occurs */ public void saveAs(File saveFile) throws DuplicateFileException, IOException { ResourceFile resourceSaveFile = new ResourceFile(saveFile); @@ -191,10 +196,14 @@ public class FileDataTypeManager extends StandAloneDataTypeManager catch (CancelledException e) { // Cancel can't happen because we are using a dummy monitor } + finally { + clearUndo(); + } } /** * Save the category to source file. + * @throws IOException if IO error occurs */ public void save() throws IOException { @@ -208,6 +217,9 @@ public class FileDataTypeManager extends StandAloneDataTypeManager catch (CancelledException e) { // Cancel can't happen because we are using a dummy monitor } + finally { + clearUndo(); + } } /** diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java index de06af07b5..8f91a6df36 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StandAloneDataTypeManager.java @@ -17,14 +17,14 @@ package ghidra.program.model.data; import java.io.Closeable; import java.io.IOException; -import java.util.Map; -import java.util.Set; +import java.util.*; import javax.help.UnsupportedOperationException; import com.google.common.collect.ImmutableList; import db.*; +import db.buffers.BufferMgr; import db.util.ErrorHandler; import generic.jar.ResourceFile; import ghidra.framework.data.OpenMode; @@ -53,9 +53,16 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB implements Clos private static final String LANGUAGE_ID = "Language ID"; private static final String COMPILER_SPEC_ID = "Compiler Spec ID"; + private static final int NUM_UNDOS = 50; + + private LinkedList undoList = new LinkedList<>(); + private LinkedList redoList = new LinkedList<>(); + private int transactionCount; private Long transaction; private boolean commitTransaction; + private String transactionName; + private boolean isImmutable; private LanguageTranslator languageUpgradeTranslator; @@ -147,6 +154,7 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB implements Clos public StandAloneDataTypeManager(String rootName) throws RuntimeIOException { super(DataOrganizationImpl.getDefaultOrganization()); this.name = rootName; + initTransactionState(); } /** @@ -160,6 +168,7 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB implements Clos throws RuntimeIOException { super(dataOrganzation); this.name = rootName; + initTransactionState(); } /** @@ -181,11 +190,12 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB implements Clos protected StandAloneDataTypeManager(ResourceFile packedDbfile, OpenMode openMode, TaskMonitor monitor) throws IOException, CancelledException { super(packedDbfile, openMode, monitor); + initTransactionState(); } /** * Constructor for a data-type manager using a specified DBHandle. - *

+ *
* NOTE: {@link #logWarning()} should be invoked immediately after * instantiating a {@link StandAloneDataTypeManager} for an existing database after * {@link #getName()} and {@link #getPath()} can be invoked safely. In addition, it @@ -207,6 +217,7 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB implements Clos if (openMode != OpenMode.CREATE && hasDataOrganizationChange(true)) { handleDataOrganizationChange(openMode, monitor); } + initTransactionState(); } /** @@ -824,6 +835,10 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB implements Clos defaultListener.categoryRenamed(this, CategoryPath.ROOT, CategoryPath.ROOT); } + protected void initTransactionState() { + dbHandle.setMaxUndos(NUM_UNDOS); + } + @Override public Transaction openTransaction(String description) throws IllegalStateException { return new Transaction() { @@ -850,6 +865,7 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB implements Clos } if (transaction == null) { transaction = dbHandle.startTransaction(); + transactionName = description; commitTransaction = true; } transactionCount++; @@ -857,30 +873,156 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB implements Clos } @Override - public void flushEvents() { - // do nothing + public void endTransaction(int transactionID, boolean commit) { + boolean restored = false; + synchronized (this) { + if (transaction == null) { + throw new IllegalStateException("No Transaction Open"); + } + if (transaction.intValue() != transactionID) { + throw new IllegalArgumentException( + "Transaction id does not match current transaction"); + } + if (!commit) { + commitTransaction = false; + } + if (--transactionCount == 0) { + try { + if (dbHandle.endTransaction(transaction.longValue(), commitTransaction)) { + redoList.clear(); + undoList.addLast(transactionName); + if (undoList.size() > NUM_UNDOS) { + undoList.removeFirst(); + } + } + else if (!commitTransaction) { + restored = true; + } + transaction = null; + } + catch (IOException e) { + dbError(e); + } + } + } + if (restored) { + notifyRestored(); + } } - @Override - public synchronized void endTransaction(int transactionID, boolean commit) { - if (transaction == null) { - throw new IllegalStateException("No Transaction Open"); - } - if (transaction.intValue() != transactionID) { - throw new IllegalArgumentException("Transaction id does not match current transaction"); - } - if (!commit) { - commitTransaction = false; - } - if (--transactionCount == 0) { + public void undo() { + synchronized (this) { + if (!canUndo()) { + return; + } try { - dbHandle.endTransaction(transaction.longValue(), commitTransaction); - transaction = null; + dbHandle.undo(); + redoList.addLast(undoList.removeLast()); } catch (IOException e) { dbError(e); } } + invalidateCache(); + notifyRestored(); + } + + public void redo() { + synchronized (this) { + if (!canRedo()) { + return; + } + try { + dbHandle.redo(); + undoList.addLast(redoList.removeLast()); + } + catch (IOException e) { + dbError(e); + } + } + invalidateCache(); + notifyRestored(); + } + + /** + * Clear undo/redo stack. + *
+ * NOTE: It is important that this always be invoked following any save operation that + * compacts the checkpoints within the database {@link BufferMgr}. + */ + protected synchronized void clearUndo() { + undoList.clear(); + redoList.clear(); + } + + /** + * Determine if there is a transaction previously undone (see {@link #undo()}) that can be + * redone (see {@link #redo()}). + * + * @return true if there is a transaction previously undone that can be redone, else false + */ + public synchronized boolean canRedo() { + return transaction == null && !redoList.isEmpty(); + } + + /** + * Determine if there is a previous transaction that can be reverted/undone (see {@link #undo()}). + * + * @return true if there is a previous transaction that can be reverted/undone, else false. + */ + public synchronized boolean canUndo() { + return transaction == null && !undoList.isEmpty(); + } + + /** + * Get the transaction name that is available for {@link #redo()} (see {@link #canRedo()}). + * @return transaction name that is available for {@link #redo()} or empty String. + */ + public synchronized String getRedoName() { + if (canRedo()) { + return redoList.getLast(); + } + return ""; + } + + /** + * Get the transaction name that is available for {@link #undo()} (see {@link #canUndo()}). + * @return transaction name that is available for {@link #undo()} or empty String. + */ + public synchronized String getUndoName() { + if (canUndo()) { + return undoList.getLast(); + } + return ""; + } + + /** + * Get all transaction names that are available within the {@link #undo()} stack. + * + * @return all transaction names that are available within the {@link #undo()} stack. + */ + public synchronized List getAllUndoNames() { + if (canUndo()) { + return new ArrayList<>(undoList); + } + return List.of(); + } + + /** + * Get all transaction names that are available within the {@link #redo()} stack. + * + * @return all transaction names that are available within the {@link #redo()} stack. + */ + public synchronized List getAllRedoNames() { + if (canRedo()) { + return new ArrayList<>(redoList); + } + return List.of(); + } + + @Override + public void flushEvents() { + // do nothing } @Override @@ -894,7 +1036,8 @@ public class StandAloneDataTypeManager extends DataTypeManagerDB implements Clos } @Override - public void close() { + public synchronized void close() { + clearUndo(); if (!dbHandle.isClosed()) { dbHandle.close(); } diff --git a/Ghidra/Processors/Atmel/ghidra_scripts/CreateAVR8GDTArchiveScript.java b/Ghidra/Processors/Atmel/ghidra_scripts/CreateAVR8GDTArchiveScript.java index 4682daca4a..a6e58f445b 100644 --- a/Ghidra/Processors/Atmel/ghidra_scripts/CreateAVR8GDTArchiveScript.java +++ b/Ghidra/Processors/Atmel/ghidra_scripts/CreateAVR8GDTArchiveScript.java @@ -43,293 +43,70 @@ import ghidra.util.Msg; public class CreateAVR8GDTArchiveScript extends GhidraScript { private File outputDirectory; - + private static String headerFilePath = "/data/HeaderFiles"; - private static String filenames[] = { - "stdint.h", - "avr/io.h", - }; - - private static String orig_args[] = { - "-I"+headerFilePath+"/avr/include", - "-I"+headerFilePath+"/avr/include/avr", - "-D__STDC", - "-D_GNU_SOURCE", - "-D__GLIBC_HAVE_LONG_LONG=1", - "-D__DOXYGEN__=true", // header files have special __attributes__ if not defined - }; - - private static String processorVariants[] = { - "AT94K", - "AT43USB320", - "AT43USB355", - "AT76C711", - "AT86RF401", - "AT90PWM1", - "AT90PWM2", - "AT90PWM2B", - "AT90PWM3", - "AT90PWM3B", - "AT90PWM216", - "AT90PWM316", - "AT90PWM161", - "AT90PWM81", - "ATmega8U2", - "ATmega16M1", - "ATmega16U2", - "ATmega16U4", - "ATmega32C1", - "ATmega32M1", - "ATmega32U2", - "ATmega32U4", - "ATmega32U6", - "ATmega64C1", - "ATmega64M1", - "ATmega128", - "ATmega128A", - "ATmega1280", - "ATmega1281", - "ATmega1284", - "ATmega1284P", - "ATmega128RFA1", - "ATmega1284RFR2", - "ATmega128RFR2", - "ATmega2564RFR2", - "ATmega256RFR2", - "ATmega2560", - "ATmega2561", - "AT90CAN32", - "AT90CAN64", - "AT90CAN128", - "AT90USB82", - "AT90USB162", - "AT90USB646", - "AT90USB647", - "AT90USB1286", - "AT90USB1287", - "ATmega644RFR2", - "ATmega64RFR2", - "ATmega64", - "ATmega64A", - "ATmega640", - "ATmega644", - "ATmega644A", - "ATmega644P", - "ATmega644PA", - "ATmega645", - "ATmega645A", - "ATmega645P", - "ATmega6450", - "ATmega6450A", - "ATmega6450P", - "ATmega649", - "ATmega649A", - "ATmega6490", - "ATmega6490A", - "ATmega6490P", - "ATmega649P", - "ATmega64HVE", - "ATmega64HVE2", - "ATmega103", - "ATmega32", - "ATmega32A", - "ATmega323", - "ATmega324P", - "ATmega324A", - "ATmega324PA", - "ATmega325", - "ATmega325A", - "ATmega325P", - "ATmega325PA", - "ATmega3250", - "ATmega3250A", - "ATmega3250P", - "ATmega3250PA", - "ATmega328P", - "ATmega328", - "ATmega329", - "ATmega329A", - "ATmega329P", - "ATmega329PA", - "ATmega3290PA", - "ATmega3290", - "ATmega3290A", - "ATmega3290P", - "ATmega32HVB", - "ATmega32HVBREVB", - "ATmega406", - "ATmega16", - "ATmega16A", - "ATmega161", - "ATmega162", - "ATmega163", - "ATmega164P", - "ATmega164A", - "ATmega164PA", - "ATmega165", - "ATmega165A", - "ATmega165P", - "ATmega165PA", - "ATmega168", - "ATmega168A", - "ATmega168P", - "ATmega168PA", - "ATmega168PB", - "ATmega169", - "ATmega169A", - "ATmega169P", - "ATmega169PA", - "ATmega8HVA", - "ATmega16HVA", - "ATmega16HVA2", - "ATmega16HVB", - "ATmega16HVBREVB", - "ATmega8", - "ATmega8A", - "ATmega48", - "ATmega48A", - "ATmega48PA", - "ATmega48PB", - "ATmega48P", - "ATmega88", - "ATmega88A", - "ATmega88P", - "ATmega88PA", - "ATmega88PB", - "ATmega8515", - "ATmega8535", - "AT90S8535", - "AT90C8534", - "AT90S8515", - "AT90S4434", - "AT90S4433", - "AT90S4414", - "ATtiny22", - "ATtiny26", - "AT90S2343", - "AT90S2333", - "AT90S2323", - "AT90S2313", - "ATtiny4", - "ATtiny5", - "ATtiny9", - "ATtiny10", - "ATtiny20", - "ATtiny40", - "ATtiny2313", - "ATtiny2313A", - "ATtiny13", - "ATtiny13A", - "ATtiny25", - "ATtiny4313", - "ATtiny45", - "ATtiny85", - "ATtiny24", - "ATtiny24A", - "ATtiny44", - "ATtiny44A", - "ATtiny441", - "ATtiny84", - "ATtiny84A", - "ATtiny841", - "ATtiny261", - "ATtiny261A", - "ATtiny461", - "ATtiny461A", - "ATtiny861", - "ATtiny861A", - "ATtiny43U", - "ATtiny48", - "ATtiny88", - "ATtiny828", - "ATtiny87", - "ATtiny167", - "ATtiny1634", - "AT90SCR100", - "ATxmega8E5", - "ATxmega16A4", - "ATxmega16A4U", - "ATxmega16C4", - "ATxmega16D4", - "ATxmega16E5", - "ATxmega32A4", - "ATxmega32A4U", - "ATxmega32C3", - "ATxmega32C4", - "ATxmega32D3", - "ATxmega32D4", - "ATxmega32E5", - "ATxmega64A1", - "ATxmega64A1U", - "ATxmega64A3", - "ATxmega64A3U", - "ATxmega64A4U", - "ATxmega64B1", - "ATxmega64B3", - "ATxmega64C3", - "ATxmega64D3", - "ATxmega64D4", - "ATxmega128A1", - "ATxmega128A1U", - "ATxmega128A4U", - "ATxmega128A3", - "ATxmega128A3U", - "ATxmega128B1", - "ATxmega128B3", - "ATxmega128C3", - "ATxmega128D3", - "ATxmega128D4", - "ATxmega192A3", - "ATxmega192A3U", - "ATxmega192C3", - "ATxmega192D3", - "ATxmega256A3", - "ATxmega256A3U", - "ATxmega256A3B", - "ATxmega256A3BU", - "ATxmega256C3", - "ATxmega256D3", - "ATxmega384C3", - "ATxmega384D3", - "ATA5702M322", - "ATA5782", - "ATA5790", - "ATA5790N", - "ATA5791", - "ATA5831", - "ATA5272", - "ATA5505", - "ATA5795", - "ATA6285", - "ATA6286", - "ATA6289", - "ATA6612C", - "ATA6613C", - "ATA6614Q", - "ATA6616C", - "ATA6617C", - "ATA664251", - "ATA8210", - "ATA8510", - "ATtiny28", - "AT90S1200", - "ATtiny15", - "ATtiny12", - "ATtiny11", - "M3000", - }; - + private static String filenames[] = { "stdint.h", "avr/io.h", }; + + private static String orig_args[] = + { "-I" + headerFilePath + "/avr/include", "-I" + headerFilePath + "/avr/include/avr", + "-D__STDC", "-D_GNU_SOURCE", "-D__GLIBC_HAVE_LONG_LONG=1", "-D__DOXYGEN__=true", // header files have special __attributes__ if not defined + }; + + private static String processorVariants[] = { "AT94K", "AT43USB320", "AT43USB355", "AT76C711", + "AT86RF401", "AT90PWM1", "AT90PWM2", "AT90PWM2B", "AT90PWM3", "AT90PWM3B", "AT90PWM216", + "AT90PWM316", "AT90PWM161", "AT90PWM81", "ATmega8U2", "ATmega16M1", "ATmega16U2", + "ATmega16U4", "ATmega32C1", "ATmega32M1", "ATmega32U2", "ATmega32U4", "ATmega32U6", + "ATmega64C1", "ATmega64M1", "ATmega128", "ATmega128A", "ATmega1280", "ATmega1281", + "ATmega1284", "ATmega1284P", "ATmega128RFA1", "ATmega1284RFR2", "ATmega128RFR2", + "ATmega2564RFR2", "ATmega256RFR2", "ATmega2560", "ATmega2561", "AT90CAN32", "AT90CAN64", + "AT90CAN128", "AT90USB82", "AT90USB162", "AT90USB646", "AT90USB647", "AT90USB1286", + "AT90USB1287", "ATmega644RFR2", "ATmega64RFR2", "ATmega64", "ATmega64A", "ATmega640", + "ATmega644", "ATmega644A", "ATmega644P", "ATmega644PA", "ATmega645", "ATmega645A", + "ATmega645P", "ATmega6450", "ATmega6450A", "ATmega6450P", "ATmega649", "ATmega649A", + "ATmega6490", "ATmega6490A", "ATmega6490P", "ATmega649P", "ATmega64HVE", "ATmega64HVE2", + "ATmega103", "ATmega32", "ATmega32A", "ATmega323", "ATmega324P", "ATmega324A", + "ATmega324PA", "ATmega325", "ATmega325A", "ATmega325P", "ATmega325PA", "ATmega3250", + "ATmega3250A", "ATmega3250P", "ATmega3250PA", "ATmega328P", "ATmega328", "ATmega329", + "ATmega329A", "ATmega329P", "ATmega329PA", "ATmega3290PA", "ATmega3290", "ATmega3290A", + "ATmega3290P", "ATmega32HVB", "ATmega32HVBREVB", "ATmega406", "ATmega16", "ATmega16A", + "ATmega161", "ATmega162", "ATmega163", "ATmega164P", "ATmega164A", "ATmega164PA", + "ATmega165", "ATmega165A", "ATmega165P", "ATmega165PA", "ATmega168", "ATmega168A", + "ATmega168P", "ATmega168PA", "ATmega168PB", "ATmega169", "ATmega169A", "ATmega169P", + "ATmega169PA", "ATmega8HVA", "ATmega16HVA", "ATmega16HVA2", "ATmega16HVB", + "ATmega16HVBREVB", "ATmega8", "ATmega8A", "ATmega48", "ATmega48A", "ATmega48PA", + "ATmega48PB", "ATmega48P", "ATmega88", "ATmega88A", "ATmega88P", "ATmega88PA", "ATmega88PB", + "ATmega8515", "ATmega8535", "AT90S8535", "AT90C8534", "AT90S8515", "AT90S4434", "AT90S4433", + "AT90S4414", "ATtiny22", "ATtiny26", "AT90S2343", "AT90S2333", "AT90S2323", "AT90S2313", + "ATtiny4", "ATtiny5", "ATtiny9", "ATtiny10", "ATtiny20", "ATtiny40", "ATtiny2313", + "ATtiny2313A", "ATtiny13", "ATtiny13A", "ATtiny25", "ATtiny4313", "ATtiny45", "ATtiny85", + "ATtiny24", "ATtiny24A", "ATtiny44", "ATtiny44A", "ATtiny441", "ATtiny84", "ATtiny84A", + "ATtiny841", "ATtiny261", "ATtiny261A", "ATtiny461", "ATtiny461A", "ATtiny861", + "ATtiny861A", "ATtiny43U", "ATtiny48", "ATtiny88", "ATtiny828", "ATtiny87", "ATtiny167", + "ATtiny1634", "AT90SCR100", "ATxmega8E5", "ATxmega16A4", "ATxmega16A4U", "ATxmega16C4", + "ATxmega16D4", "ATxmega16E5", "ATxmega32A4", "ATxmega32A4U", "ATxmega32C3", "ATxmega32C4", + "ATxmega32D3", "ATxmega32D4", "ATxmega32E5", "ATxmega64A1", "ATxmega64A1U", "ATxmega64A3", + "ATxmega64A3U", "ATxmega64A4U", "ATxmega64B1", "ATxmega64B3", "ATxmega64C3", "ATxmega64D3", + "ATxmega64D4", "ATxmega128A1", "ATxmega128A1U", "ATxmega128A4U", "ATxmega128A3", + "ATxmega128A3U", "ATxmega128B1", "ATxmega128B3", "ATxmega128C3", "ATxmega128D3", + "ATxmega128D4", "ATxmega192A3", "ATxmega192A3U", "ATxmega192C3", "ATxmega192D3", + "ATxmega256A3", "ATxmega256A3U", "ATxmega256A3B", "ATxmega256A3BU", "ATxmega256C3", + "ATxmega256D3", "ATxmega384C3", "ATxmega384D3", "ATA5702M322", "ATA5782", "ATA5790", + "ATA5790N", "ATA5791", "ATA5831", "ATA5272", "ATA5505", "ATA5795", "ATA6285", "ATA6286", + "ATA6289", "ATA6612C", "ATA6613C", "ATA6614Q", "ATA6616C", "ATA6617C", "ATA664251", + "ATA8210", "ATA8510", "ATtiny28", "AT90S1200", "ATtiny15", "ATtiny12", "ATtiny11", + "M3000", }; + @Override protected void run() throws Exception { outputDirectory = askDirectory("Select Directory for GDT files", "Select GDT Output Dir"); - + parseGDT_AVR8(); } - - public void parseGDT_AVR8() throws Exception { + + public void parseGDT_AVR8() throws Exception { // If need data types from other archives can add other archives - + // Using another archive while parsing will cause: // - a dependence on the other archive // - any missing data types while parsing are supplied if present from existingDTMgr @@ -345,21 +122,21 @@ public class CreateAVR8GDTArchiveScript extends GhidraScript { // by defaults, don't want to be dependent on other archives if have all necessary definitions // comment out if missing data types openTypes = null; - + String dataTypeFile = outputDirectory + File.separator + "avr8.gdt"; File f = getArchiveFile(dataTypeFile); - - FileDataTypeManager dtMgr = FileDataTypeManager.createFileArchive(f); - - // Parse each processor variant as an individual parse that gets added to the data - // type manager. If all header files were parsed at once, there are conflicting - // macro definitions that will cause the parse to fail. - // - for (String variantName : processorVariants) { - parseProcessorDefs(variantName, dtMgr, openTypes); - } - + + FileDataTypeManager dtMgr = FileDataTypeManager.createFileArchive(f); + + // Parse each processor variant as an individual parse that gets added to the data + // type manager. If all header files were parsed at once, there are conflicting + // macro definitions that will cause the parse to fail. + // + for (String variantName : processorVariants) { + parseProcessorDefs(variantName, dtMgr, openTypes); + } + dtMgr.save(); dtMgr.close(); } @@ -394,15 +171,17 @@ public class CreateAVR8GDTArchiveScript extends GhidraScript { * @throws ghidra.app.util.cparser.C.ParseException * @throws IOException io exception */ - private void parseProcessorDefs(String procName, FileDataTypeManager dtMgr, DataTypeManager[] openTypes) + private void parseProcessorDefs(String procName, FileDataTypeManager dtMgr, + DataTypeManager[] openTypes) throws ParseException, ghidra.app.util.cparser.C.ParseException, IOException { - - String args[] = Arrays.append(orig_args, "-D__AVR_"+procName+"__"); - - CParseResults results = CParserUtils.parseHeaderFiles(openTypes, filenames, args, dtMgr, "avr8:LE:16:atmega256", "gcc", monitor); - + + String args[] = Arrays.append(orig_args, "-D__AVR_" + procName + "__"); + + CParseResults results = CParserUtils.parseHeaderFiles(openTypes, filenames, args, dtMgr, + "avr8:LE:16:atmega256", "gcc", monitor); + Msg.info(this, results.getFormattedParseMessage(null)); - + storeExtraDefinitions(procName, dtMgr, openTypes, results.preProcessor()); } @@ -413,48 +192,54 @@ public class CreateAVR8GDTArchiveScript extends GhidraScript { * @param dtMgr add data types to dtMgr * @param cpp pre-processor holds macros/defines from parsing */ - private void storeExtraDefinitions(String procName, FileDataTypeManager dtMgr, DataTypeManager[] openTypes, PreProcessor cpp) { + private void storeExtraDefinitions(String procName, FileDataTypeManager dtMgr, + DataTypeManager[] openTypes, PreProcessor cpp) { int transactionID = dtMgr.startTransaction("Add Extra Equates"); - - DefineTable definitions = cpp.getDefinitions(); - Iterator defineNames = definitions.getDefineNames(); - while (defineNames.hasNext()) { - String defName = defineNames.next(); - String rawDefValue = definitions.getValue(defName); - String expandValue = definitions.expandDefine(defName); - - if (expandValue == null || expandValue.length()==0) { - // can't expand, must be a macro - continue; + try { + DefineTable definitions = cpp.getDefinitions(); + Iterator defineNames = definitions.getDefineNames(); + while (defineNames.hasNext()) { + String defName = defineNames.next(); + String rawDefValue = definitions.getValue(defName); + String expandValue = definitions.expandDefine(defName); + + if (expandValue == null || expandValue.length() == 0) { + // can't expand, must be a macro + continue; + } + + // look at string and see if if the definition of an SFR, register + String PTR_PREFIX_16 = "(*(volatile uint16_t *)"; + String PTR_PREFIX_8 = "(*(volatile uint8_t *)"; + + Long lvalue = null; + if (expandValue.startsWith(PTR_PREFIX_16)) { + // ptr to 16 bit address in SFR + expandValue = expandValue.replace(PTR_PREFIX_16, ""); + expandValue = expandValue.substring(0, expandValue.lastIndexOf(')')); + } + else if (expandValue.startsWith(PTR_PREFIX_8)) { + // ptr to 8 bit address in SFR + expandValue = expandValue.replace(PTR_PREFIX_8, ""); + expandValue = expandValue.substring(0, expandValue.lastIndexOf(')')); + } + else { + continue; + } + + if (expandValue == null || expandValue.length() == 0) { + continue; + } + + lvalue = AddressEvaluator.evaluateToLong(expandValue); + if (lvalue == null) { + continue; + } + definitions.populateDefineEquate(openTypes, dtMgr, "memory", "", defName, lvalue); } - - // look at string and see if if the definition of an SFR, register - String PTR_PREFIX_16 = "(*(volatile uint16_t *)"; - String PTR_PREFIX_8 = "(*(volatile uint8_t *)"; - - Long lvalue = null; - if (expandValue.startsWith(PTR_PREFIX_16)) { - // ptr to 16 bit address in SFR - expandValue = expandValue.replace(PTR_PREFIX_16, ""); - expandValue = expandValue.substring(0,expandValue.lastIndexOf(')')); - } else if (expandValue.startsWith(PTR_PREFIX_8) ) { - // ptr to 8 bit address in SFR - expandValue = expandValue.replace(PTR_PREFIX_8, ""); - expandValue = expandValue.substring(0,expandValue.lastIndexOf(')')); - } else { - continue; - } - - if (expandValue == null || expandValue.length() == 0) { - continue; - } - - lvalue = AddressEvaluator.evaluateToLong(expandValue); - if (lvalue == null) { - continue; - } - definitions.populateDefineEquate(openTypes, dtMgr, "memory", "", defName, lvalue); } - dtMgr.endTransaction(transactionID, true); + finally { + dtMgr.endTransaction(transactionID, true); + } } }