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 578a557656..8e78310760 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 @@ -248,17 +248,17 @@ public abstract class CompEditorModel extends CompositeEdit return true; } - @Override - public DataTypeInstance validateComponentDataType(int rowIndex, String dtString) - throws UsrException { - dtString = DataTypeHelper.stripWhiteSpace(dtString); - if ((dtString == null) || (dtString.length() < 1)) { - if (rowIndex == getNumComponents()) { - return null; - } - } - return super.validateComponentDataType(rowIndex, dtString); - } +// @Override +// public DataTypeInstance validateComponentDataType(int rowIndex, String dtString) +// throws UsrException { +// dtString = DataTypeHelper.stripWhiteSpace(dtString); +// if ((dtString == null) || (dtString.length() < 1)) { +// if (rowIndex == getNumComponents()) { +// return null; +// } +// } +// return super.validateComponentDataType(rowIndex, dtString); +// } @Override public boolean isAddAllowed(DataType dataType) { @@ -894,18 +894,18 @@ public abstract class CompEditorModel extends CompositeEdit protected abstract void replaceOriginalComponents(); @Override - protected void checkIsAllowableDataType(DataType datatype) throws InvalidDataTypeException { + protected void checkIsAllowableDataType(DataType dataType) throws InvalidDataTypeException { - super.checkIsAllowableDataType(datatype); + super.checkIsAllowableDataType(dataType); // Verify that we aren't adding this structure or anything that it is // part of to this editable structure. - if (datatype.equals(viewComposite)) { - String msg = "Data type \"" + datatype.getDisplayName() + "\" can't contain itself."; + if (dataType.equals(viewComposite)) { + String msg = "Data type \"" + dataType.getDisplayName() + "\" can't contain itself."; throw new InvalidDataTypeException(msg); } - else if (DataTypeUtilities.isSecondPartOfFirst(datatype, viewComposite)) { - String msg = "Data type \"" + datatype.getDisplayName() + "\" has \"" + + else if (DataTypeUtilities.isSecondPartOfFirst(dataType, viewComposite)) { + String msg = "Data type \"" + dataType.getDisplayName() + "\" has \"" + viewComposite.getDisplayName() + "\" within it."; throw new InvalidDataTypeException(msg); } @@ -1314,25 +1314,25 @@ public abstract class CompEditorModel extends CompositeEdit return; } - DataType dataType = viewDTM.getDataType(path.getCategoryPath(), path.getDataTypeName()); + DataType dataType = viewDTM.getDataType(path); if (dataType == null) { return; } if (!path.equals(originalDataTypePath)) { - DataType dt = viewDTM.getDataType(path); - if (dt != null) { - if (hasSubDt(viewComposite, path)) { - String msg = "Removed sub-component data type \"" + path; - setStatus(msg, true); - } - viewDTM.withTransaction("Removed Dependency", () -> { - viewDTM.clearUndoOnChange(); - viewDTM.remove(dt, TaskMonitor.DUMMY); - }); - fireTableDataChanged(); - componentDataChanged(); + if (!viewDTM.isViewDataTypeFromOriginalDTM(dataType)) { + return; } + if (hasSubDt(viewComposite, path)) { + String msg = "Removed sub-component data type \"" + path; + setStatus(msg, true); + } + viewDTM.withTransaction("Removed Dependency", () -> { + viewDTM.clearUndoOnChange(); + viewDTM.remove(dataType, TaskMonitor.DUMMY); + }); + fireTableDataChanged(); + componentDataChanged(); return; } @@ -1366,56 +1366,7 @@ public abstract class CompEditorModel extends CompositeEdit @Override public void dataTypeRenamed(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath) { - - if (dtm != originalDTM) { - throw new AssertException("Listener only supports original DTM"); - } - - if (!isLoaded()) { - return; - } - - if (oldPath.getDataTypeName().equals(newPath.getDataTypeName())) { - return; - } - - String newName = newPath.getDataTypeName(); - String oldName = oldPath.getDataTypeName(); - - // Does the old name match our original name. - // Check originalCompositeId to ensure original type is managed - if (originalCompositeId != DataTypeManager.NULL_DATATYPE_ID && - oldPath.equals(originalDataTypePath)) { - originalDataTypePath = newPath; - try { - if (viewComposite.getName().equals(oldName)) { - setName(newName); - } - } - catch (InvalidNameException | DuplicateNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - return; - } - - // Check for managed datatype changing - DataType dt = viewDTM.getDataType(oldPath); - if (dt == null) { - return; - } - - viewDTM.withTransaction("Renamed Dependency", () -> { - viewDTM.clearUndoOnChange(); - try { - dt.setName(newPath.getDataTypeName()); - } - catch (InvalidNameException | DuplicateNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - }); - - fireTableDataChanged(); - componentDataChanged(); + dataTypeMoved(dtm, oldPath, newPath); } @Override @@ -1429,31 +1380,64 @@ public abstract class CompEditorModel extends CompositeEdit return; } - DataType dt = viewDTM.getDataType(oldPath); - if (dt == null) { + if (oldPath.equals(newPath)) { return; } - try { - viewDTM.withTransaction("Moved " + oldPath, () -> { - viewDTM.clearUndoOnChange(); - Category newDtCat = viewDTM.createCategory(newPath.getCategoryPath()); - newDtCat.moveDataType(dt, null); - }); - } - catch (DataTypeDependencyException e) { - throw new AssertException(e); - } + String newName = newPath.getDataTypeName(); + String oldName = oldPath.getDataTypeName(); - if (originalDataTypePath.getDataTypeName().equals(newPath.getDataTypeName()) && - originalDataTypePath.getCategoryPath().equals(oldPath.getCategoryPath())) { - originalDataTypePath = newPath; + CategoryPath newCategoryPath = newPath.getCategoryPath(); + CategoryPath oldCategoryPath = oldPath.getCategoryPath(); + + // Does the old name match our original name. + // Check originalCompositeId to ensure original type is managed + if (originalCompositeId != DataTypeManager.NULL_DATATYPE_ID && + oldPath.equals(originalDataTypePath)) { + + viewDTM.withTransaction("Name Changed", () -> { + viewDTM.clearUndoOnChange(); + originalDataTypePath = newPath; + try { + if (viewComposite.getName().equals(oldName)) { + setName(newName); + } + if (!newCategoryPath.equals(oldCategoryPath)) { + viewComposite.setCategoryPath(newCategoryPath); + } + } + catch (InvalidNameException | DuplicateNameException e) { + Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); + } + }); compositeInfoChanged(); } else { - fireTableDataChanged(); - componentDataChanged(); + // Check for managed datatype changing + DataType originalDt = originalDTM.getDataType(newPath); + if (!(originalDt instanceof DatabaseObject)) { + return; + } + DataType dt = viewDTM.findMyDataTypeFromOriginalID(originalDTM.getID(originalDt)); + if (dt == null) { + return; + } + viewDTM.withTransaction("Renamed Dependency", () -> { + viewDTM.clearUndoOnChange(); + try { + dt.setName(newName); + if (!newCategoryPath.equals(oldCategoryPath)) { + dt.setCategoryPath(newCategoryPath); + } + } + catch (InvalidNameException | DuplicateNameException e) { + Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); + } + }); } + + fireTableDataChanged(); + componentDataChanged(); } @Override @@ -1517,11 +1501,10 @@ public abstract class CompEditorModel extends CompositeEdit // potentially many types getting changed by one change. DataType changedDt = originalDTM.getDataType(path); if (!(changedDt instanceof DatabaseObject)) { - // NOTE: viewDTM only maps view-to-original IDs for DataTypeDB return; } - long originalId = originalDTM.getID(changedDt); - DataType viewDt = viewDTM.findMyDataTypeFromOriginalID(originalId); + DataType viewDt = + viewDTM.findMyDataTypeFromOriginalID(originalDTM.getID(changedDt)); if (viewDt == null) { return; } @@ -1562,30 +1545,32 @@ public abstract class CompEditorModel extends CompositeEdit if (!oldPath.equals(originalDataTypePath)) { // Check for type which may be referenced by viewComposite DataType dt = viewDTM.getDataType(oldPath); - if (dt != null) { - if (hasSubDt(viewComposite, oldPath)) { - String msg = "Replaced data type \"" + oldPath + - "\", which is a sub-component of \"" + getOriginalDataTypeName() + "\"."; - setStatus(msg, true); - } - // NOTE: depending upon event sequence and handling a - // re-load may have occurred and replacement may be unnecessary - try { - viewDTM.withTransaction("Replaced Dependency", () -> { - viewDTM.clearUndoOnChange(); - viewDTM.replaceDataType(dt, newDataType, true); - }); - } - catch (DataTypeDependencyException e) { - throw new AssertException(e); - } - - // Clear undo/redo stack to avoid inconsistency with originalDTM - viewDTM.clearUndo(); - - fireTableDataChanged(); - componentDataChanged(); + if (dt == null || !viewDTM.isViewDataTypeFromOriginalDTM(dt)) { + return; } + + if (hasSubDt(viewComposite, oldPath)) { + String msg = "Replaced data type \"" + oldPath + + "\", which is a sub-component of \"" + getOriginalDataTypeName() + "\"."; + setStatus(msg, true); + } + // NOTE: depending upon event sequence and handling a + // re-load may have occurred and replacement may be unnecessary + try { + viewDTM.withTransaction("Replaced Dependency", () -> { + viewDTM.clearUndoOnChange(); + viewDTM.replaceDataType(dt, newDataType, true); + }); + } + catch (DataTypeDependencyException e) { + throw new AssertException(e); + } + + // Clear undo/redo stack to avoid inconsistency with originalDTM + viewDTM.clearUndo(); + + fireTableDataChanged(); + componentDataChanged(); return; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorModel.java index d0e7f7ff33..6e23dd0d9f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorModel.java @@ -157,7 +157,7 @@ abstract public class CompositeEditorModel extends Composit originalDataTypePath = originalComposite.getDataTypePath(); currentName = dataType.getName(); - createViewCompositeFromOriginalComposite(originalComposite); + createViewCompositeFromOriginalComposite(); // Listen so we can update editor if name changes for this structure. originalDTM.addDataTypeManagerListener(this); @@ -204,12 +204,10 @@ abstract public class CompositeEditorModel extends Composit } /** - * Create {@code viewComposite} and associated view datatype manager ({@code viewDTM}) and - * changes listener(s) if required. - * - * @param original original composite being loaded + * Create {@code viewComposite} and {@link CompositeViewerDataTypeManager viewDTM} for this + * editor and the {@code originalComposite}. */ - protected void createViewCompositeFromOriginalComposite(T original) { + protected void createViewCompositeFromOriginalComposite() { if (viewDTM != null) { viewDTM.close(); @@ -217,8 +215,9 @@ abstract public class CompositeEditorModel extends Composit } // Use temporary standalone view datatype manager - viewDTM = new CompositeViewerDataTypeManager<>(original.getDataTypeManager().getName(), - original, this::componentEdited, this::restoreEditor); + viewDTM = + new CompositeViewerDataTypeManager<>(originalComposite.getDataTypeManager().getName(), + originalComposite, this::componentEdited, this::restoreEditor); viewComposite = viewDTM.getResolvedViewComposite(); @@ -230,7 +229,7 @@ abstract public class CompositeEditorModel extends Composit // underlying datatype default setting value being presented when adjusting component // default settings. viewDTM.withTransaction("Load Settings", - () -> cloneAllComponentSettings(original, viewComposite)); + () -> cloneAllComponentSettings(originalComposite, viewComposite)); viewDTM.clearUndo(); } @@ -317,7 +316,6 @@ abstract public class CompositeEditorModel extends Composit throw new InvalidDataTypeException("Data types of size 0 are not allowed."); } - // TODO: Need to handle proper placement for big-endian within a larger component (i.e., right-justified) return DataTypeInstance.getDataTypeInstance(resultDt, resultLen, viewComposite.isPackingEnabled()); } @@ -481,13 +479,13 @@ abstract public class CompositeEditorModel extends Composit dtName = previousDt.getDisplayName(); } DataType newDt = null; - int newLength = -1; + int newLength; if (dataTypeObject instanceof DataTypeInstance dti) { - newDt = resolve(dti.getDataType()); + newDt = dti.getDataType(); newLength = dti.getLength(); } else if (dataTypeObject instanceof DataType dt) { - newDt = resolve(dt); + newDt = dt; newLength = newDt.getLength(); } else if (dataTypeObject instanceof String dtString) { @@ -506,6 +504,9 @@ abstract public class CompositeEditorModel extends Composit newLength = 0; } + DataType dataType = newDt.clone(originalDTM); + newLength = newDt.getLength(); + checkIsAllowableDataType(newDt); if (newLength < 0) { @@ -535,8 +536,7 @@ abstract public class CompositeEditorModel extends Composit } // Set component datatype and length on view composite - DataType dataType = resolve(newDt); // probably already resolved - setComponentDataTypeInstance(rowIndex, dataType, newLength); + setComponentDataTypeInstance(rowIndex, newDt, newLength); return true; }); @@ -1434,51 +1434,51 @@ abstract public class CompositeEditorModel extends Composit * @return a valid data type instance or null if at blank line with no data type name. * @throws UsrException indicating that the data type is not valid. */ - protected DataTypeInstance validateComponentDataType(int rowIndex, String dtString) - throws UsrException { - DataType dt = null; - String dtName = ""; - dtString = DataTypeHelper.stripWhiteSpace(dtString); - DataTypeComponent element = getComponent(rowIndex); - if (element != null) { - dt = element.getDataType(); - dtName = dt.getDisplayName(); - if (dtString.equals(dtName)) { - return DataTypeInstance.getDataTypeInstance(element.getDataType(), - element.getLength(), usesAlignedLengthComponents()); - } - } - - int newLength = 0; - DataType newDt = DataTypeHelper.parseDataType(rowIndex, dtString, this, originalDTM, - provider.dtmService); - if (newDt == null) { - if (dt != null) { - throw new UsrException("No data type was specified."); - } - throw new AssertException("Can't set data type to null."); - } - - checkIsAllowableDataType(newDt); - - newLength = newDt.getLength(); - if (newLength < 0) { - DataTypeInstance sizedDataType = DataTypeHelper.getSizedDataType(provider, newDt, - lastNumBytes, getMaxReplaceLength(rowIndex)); - newLength = sizedDataType.getLength(); - } - - newDt = viewDTM.resolve(newDt, null); - int maxLength = getMaxReplaceLength(rowIndex); - if (newLength <= 0) { - throw new UsrException("Can't currently add this data type."); - } - if (maxLength > 0 && newLength > maxLength) { - throw new UsrException(newDt.getDisplayName() + " doesn't fit."); - } - return DataTypeInstance.getDataTypeInstance(newDt, newLength, - usesAlignedLengthComponents()); - } +// protected DataTypeInstance validateComponentDataType(int rowIndex, String dtString) +// throws UsrException { +// DataType dt = null; +// String dtName = ""; +// dtString = DataTypeHelper.stripWhiteSpace(dtString); +// DataTypeComponent element = getComponent(rowIndex); +// if (element != null) { +// dt = element.getDataType(); +// dtName = dt.getDisplayName(); +// if (dtString.equals(dtName)) { +// return DataTypeInstance.getDataTypeInstance(element.getDataType(), +// element.getLength(), usesAlignedLengthComponents()); +// } +// } +// +// int newLength = 0; +// DataType newDt = DataTypeHelper.parseDataType(rowIndex, dtString, this, originalDTM, +// provider.dtmService); +// if (newDt == null) { +// if (dt != null) { +// throw new UsrException("No data type was specified."); +// } +// throw new AssertException("Can't set data type to null."); +// } +// +// checkIsAllowableDataType(newDt); +// +// newLength = newDt.getLength(); +// if (newLength < 0) { +// DataTypeInstance sizedDataType = DataTypeHelper.getSizedDataType(provider, newDt, +// lastNumBytes, getMaxReplaceLength(rowIndex)); +// newLength = sizedDataType.getLength(); +// } +// +// newDt = viewDTM.resolve(newDt, null); +// int maxLength = getMaxReplaceLength(rowIndex); +// if (newLength <= 0) { +// throw new UsrException("Can't currently add this data type."); +// } +// if (maxLength > 0 && newLength > maxLength) { +// throw new UsrException(newDt.getDisplayName() + " doesn't fit."); +// } +// return DataTypeInstance.getDataTypeInstance(newDt, newLength, +// usesAlignedLengthComponents()); +// } @SuppressWarnings("unused") // the exception is thrown by subclasses protected void validateComponentName(int rowIndex, String name) throws UsrException { 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 465f2c949b..2c91aef45b 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 @@ -47,8 +47,7 @@ import utilities.util.reflection.ReflectionUtilities; * @param Specific {@link CompositeEditorModel} implementation which supports editing T */ public abstract class CompositeEditorProvider> - extends ComponentProviderAdapter - implements EditorProvider, EditorActionListener { + extends ComponentProviderAdapter implements EditorProvider, EditorActionListener { protected static final Icon EDITOR_ICON = new GIcon("icon.plugin.composite.editor.provider"); @@ -317,7 +316,7 @@ public abstract class CompositeEditorProvider extends StandAl private TreeSet orphanIds = new TreeSet<>(); /** - * Creates a data type manager that the structure editor will use internally for managing - * dependencies for an unmanaged structure being edited. A single transaction will be started - * with this instantiation and held open until this instance is closed. Undo/redo is - * not be supported. + * Creates a data type manager that the composite editor will use internally for managing + * dependencies without resolving the actual composite being edited. A single transaction + * will be started with this instantiation and held open until this instance is closed. + * Undo/redo and datatype pruning is not be supported. * @param rootName the root name for this data type manager (usually the program name). * @param originalDTM the original data type manager. */ public CompositeViewerDataTypeManager(String rootName, DataTypeManager originalDTM) { this(rootName, originalDTM, null, null, null); - clearUndo(); transactionId = startTransaction("Composite Edit"); } @@ -123,8 +124,7 @@ public class CompositeViewerDataTypeManager extends StandAl @SuppressWarnings("unchecked") private T resolveViewComposite() { - return originalComposite != null ? (T) super.resolve(originalComposite, null) - : null; + return originalComposite != null ? (T) super.resolve(originalComposite, null) : null; } private void initializeArchitecture() { @@ -158,12 +158,18 @@ public class CompositeViewerDataTypeManager extends StandAl @Override public void undo() { + if (!isUndoRedoAllowed()) { + throw new UnsupportedOperationException(); + } dataTypeIDMap.invalidate(); super.undo(); } @Override public void redo() { + if (!isUndoRedoAllowed()) { + throw new UnsupportedOperationException(); + } dataTypeIDMap.invalidate(); super.redo(); } @@ -237,10 +243,12 @@ public class CompositeViewerDataTypeManager extends StandAl public DataType replaceDataType(DataType existingViewDt, DataType replacementDt, boolean updateCategoryPath) throws DataTypeDependencyException { - long viewDtId = getID(existingViewDt); + if (existingViewDt.getDataTypeManager() != this) { + throw new IllegalArgumentException("datatype is not from this manager"); + } if (existingViewDt instanceof DatabaseObject) { - dataTypeIDMap.remove(viewDtId); + dataTypeIDMap.remove(getID(existingViewDt)); } DataType newResolvedDt = @@ -259,10 +267,12 @@ public class CompositeViewerDataTypeManager extends StandAl @Override public boolean remove(DataType existingViewDt, TaskMonitor monitor) { - long viewDtId = getID(existingViewDt); + if (existingViewDt.getDataTypeManager() != this) { + throw new IllegalArgumentException("datatype is not from this manager"); + } if (existingViewDt instanceof DatabaseObject) { - dataTypeIDMap.remove(viewDtId); + dataTypeIDMap.remove(getID(existingViewDt)); } return super.remove(existingViewDt, monitor); @@ -373,7 +383,7 @@ public class CompositeViewerDataTypeManager extends StandAl if (committed && dataTypeChanged && changeCallback != null) { Swing.runLater(() -> changeCallback.call()); } - + if (getTransactionCount() == 0) { dataTypeChanged = false; } @@ -432,14 +442,53 @@ public class CompositeViewerDataTypeManager extends StandAl } } - public DataType findOriginalDataTypeFromMyID(long myId) { - Long originalId = dataTypeIDMap.getOriginalIDFromViewID(myId); - return originalId != null ? originalDTM.getDataType(originalId) : null; - } - + /** + * Find a resolved DB-datatype within this manager based upon its source datatype's ID + * within the original datatype manager associated with this manager. This method is + * useful when attempting to matchup a datatype within this manager to one which has changed + * within the original datatype manager. + * + * @param originalId datatype ID within original datatype manager + * @return matching DB-datatype or null if not found + */ public DataType findMyDataTypeFromOriginalID(long originalId) { Long myId = dataTypeIDMap.getViewIDFromOriginalID(originalId); return myId != null ? getDataType(myId) : null; } + /** + * Find a resolved DB-datatype within the original datatype manager based upon a resolved + * datatype's ID within this manager. This method is useful when attempting to matchup a + * datatype within this manager to one which has possibly changed within the original + * datatype manager. + * + * @param myId resolved datatype ID within this datatype manager + * @return matching DB-datatype or null if not found + */ + public DataType findOriginalDataTypeFromMyID(long myId) { + Long originalId = dataTypeIDMap.getOriginalIDFromViewID(myId); + return originalId != null ? originalDTM.getDataType(originalId) : null; + } + + /** + * Determine if the specified datatype which has previsouly been resolved to this datatype + * manager originated from original composite's source (e.g., program). + *

+ * NOTE: Non-DB datatypes will always return false. + * + * @param existingViewDt existing datatype which has previously been resolved to this + * datatype manager. + * @return true if specified datatype originated from this manager's associated original + * datatype manager. + */ + public boolean isViewDataTypeFromOriginalDTM(DataType existingViewDt) { + if (existingViewDt.getDataTypeManager() != this) { + throw new IllegalArgumentException("datatype is not from this manager"); + } + if (!(existingViewDt instanceof DatabaseObject)) { + return false; + } + return dataTypeIDMap.getOriginalIDFromViewID(getID(existingViewDt)) != null; + } + } 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 2f880213d6..dd2b56853f 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 @@ -199,35 +199,35 @@ abstract class CompositeViewerModel extends AbstractTableMo } } - /** - * Resolves the data type against the indicated data type manager using the specified - * conflictHandler. In general, a transaction should have already been initiated prior to - * calling this method so that the true nature of the transaction may be established for - * use with undo/redo (e.g., Set Datatype). - * - * @param dataType the data type to be resolved - * @param resolveDtm the data type manager to resolve the data type against - * @param conflictHandler the handler to be used for any conflicts encountered while resolving - * @return the resolved data type - */ - protected final DataType resolveDataType(DataType dataType, DataTypeManager resolveDtm, - DataTypeConflictHandler conflictHandler) { - if (resolveDtm == null || dataType == DataType.DEFAULT) { - return DataType.DEFAULT; - } - return resolveDtm.withTransaction("Resolve " + dataType.getPathName(), () -> { - return resolveDtm.resolve(dataType, conflictHandler); - }); - } - - /** - * Resolves the indicated data type against the working copy in the viewer's data type manager. - * @param dataType the data type - * @return the working copy of the data type. - */ - public DataType resolve(DataType dataType) { - return resolveDataType(dataType, viewDTM, null); - } +// /** +// * Resolves the data type against the indicated data type manager using the specified +// * conflictHandler. In general, a transaction should have already been initiated prior to +// * calling this method so that the true nature of the transaction may be established for +// * use with undo/redo (e.g., Set Datatype). +// * +// * @param dataType the data type to be resolved +// * @param resolveDtm the data type manager to resolve the data type against +// * @param conflictHandler the handler to be used for any conflicts encountered while resolving +// * @return the resolved data type +// */ +// protected final DataType resolveDataType(DataType dataType, DataTypeManager resolveDtm, +// DataTypeConflictHandler conflictHandler) { +// if (resolveDtm == null || dataType == DataType.DEFAULT) { +// return DataType.DEFAULT; +// } +// return resolveDtm.withTransaction("Resolve " + dataType.getPathName(), () -> { +// return resolveDtm.resolve(dataType, conflictHandler); +// }); +// } +// +// /** +// * Resolves the indicated data type against the working copy in the viewer's data type manager. +// * @param dataType the data type +// * @return the working copy of the data type. +// */ +// public DataType resolve(DataType dataType) { +// return resolveDataType(dataType, viewDTM, null); +// } /** * Gets the current row diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/BiDirectionDataType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/BiDirectionDataType.java deleted file mode 100644 index 9294843b53..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/BiDirectionDataType.java +++ /dev/null @@ -1,726 +0,0 @@ -/* ### - * 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.stackeditor; - -import java.util.*; - -import javax.help.UnsupportedOperationException; - -import ghidra.program.model.data.*; -import ghidra.util.exception.AssertException; - -/** - * {@link BiDirectionDataType} is a special structure data type that allows both positive and - * negative offset values. - *

- * NOTE: This special purpose datatype does not support resolving with a {@link DataTypeManager} - */ -public abstract class BiDirectionDataType extends StructureDataType - implements BiDirectionStructure { - - protected static Comparator ordinalComparator = new OrdinalComparator(); - protected static Comparator offsetComparator = new OffsetComparator(); - protected int negativeLength; - protected int positiveLength; - protected int splitOffset; // division offset between negative/positive halves - - /** - * Construct {@link BiDirectionDataType} - * @param name data type display name - * @param negativeLength negative allocation size - * @param positiveLength positive allocation size - * @param splitOffset division offset between negative/positive halves - * @param dtm associated datatype manager for component datatypes - */ - protected BiDirectionDataType(String name, int negativeLength, int positiveLength, - int splitOffset, DataTypeManager dtm) { - super(CategoryPath.ROOT, name, negativeLength + positiveLength, dtm); - this.negativeLength = negativeLength; - this.positiveLength = positiveLength; - this.splitOffset = splitOffset; - } - - @Override - protected DataType validateDataType(DataType dataType) { - if (DataTypeComponent.usesZeroLengthComponent(dataType)) { - throw new IllegalArgumentException( - "Zero-length datatype not permitted: " + dataType.getName()); - } - if (dataType instanceof BitFieldDataType) { - throw new IllegalArgumentException("Bitfield not permitted: " + dataType.getName()); - } - return super.validateDataType(dataType); - } - - @Override - public int getAlignment() { - throw new UnsupportedOperationException( - "BiDirectionDataType.getAlignment() not implemented."); - } - - @Override - public boolean repack(boolean notify) { - throw new AssertException(); - } - - @Override - public void setToDefaultAligned() { - // ignore - } - - @Override - public void setToMachineAligned() { - // ignore - } - - @Override - public void setPackingEnabled(boolean aligned) { - // ignore - } - - @Override - public void setExplicitPackingValue(int packingValue) { - // ignore - } - - @Override - public void setExplicitMinimumAlignment(int minimumAlignment) { - // ignore - } - - protected DataTypeComponent getDefinedComponentAt(int offset) { - if (offset < splitOffset - negativeLength || offset >= splitOffset + positiveLength) { - return null; - } - int index = Collections.binarySearch(components, Integer.valueOf(offset), offsetComparator); - if (index >= 0) { - return components.get(index); - } - return null; - } - - @Override - public DataTypeComponent getComponentAt(int offset) { - if (offset < splitOffset - negativeLength || offset >= splitOffset + positiveLength) { - return null; - } - int index = Collections.binarySearch(components, Integer.valueOf(offset), offsetComparator); - if (index >= 0) { - return components.get(index); - } - int ordinal = 0; - index = -index - 1; - int prevIndex = index - 1; - if (prevIndex < 0) { - ordinal = offset + negativeLength - splitOffset; - } - else { - DataTypeComponent prevComp = components.get(prevIndex); - int prevOrdinal = prevComp.getOrdinal(); - int prevOffset = prevComp.getOffset(); - int endOffset = prevComp.getEndOffset(); - if (offset > prevOffset && offset <= endOffset) { - return null; - } - ordinal = prevOrdinal + offset - endOffset; - } - return new DataTypeComponentImpl(DataType.DEFAULT, this, 1, ordinal, offset); - } - - @Override - public int getSplitOffset() { - return splitOffset; - } - - @Override - public int getNegativeLength() { - return negativeLength; - } - - @Override - public int getPositiveLength() { - return positiveLength; - } - - @Override - public void delete(int index) { - if (index < 0 || index >= numComponents) { - throw new IndexOutOfBoundsException(index); - } - DataTypeComponent comp = getComponent(index); - int offset = comp.getOffset(); - int length = comp.getLength(); - int idx = Collections.binarySearch(components, index, ordinalComparator); - if (idx >= 0) { - DataTypeComponent dtc = components.remove(idx); - dtc.getDataType().removeParent(this); - length = dtc.getLength(); - } - else { - idx = -idx - 1; - length = 1; - } - adjustOffsets(idx, offset, -1, -length); - numComponents--; - notifySizeChanged(); - } - - @Override - public void delete(Set ordinals) { - for (int ordinal : ordinals) { - delete(ordinal); - } - } - - /** - * - * @param idx the min index in the defined component arraylist - * @param offset - * @param deltaOrdinal - * @param deltaLength - */ - protected void adjustOffsets(int idx, int offset, int deltaOrdinal, int deltaLength) { - if (offset >= splitOffset) { - // component was in positive offsets - shiftOffsets(idx, deltaOrdinal, deltaLength); - positiveLength += deltaLength; - } - else { - if (offset - deltaLength > splitOffset) { - // The deleted component straddled negative/zero boudary. - shiftOffsets(0, idx - 1, 0, offset); - shiftOffsets(idx, deltaOrdinal, offset - deltaLength); -// TODO: this seems wrong - negativeLength += offset; - positiveLength -= (offset - deltaLength); - } - else { - // component was in negative offsets - shiftOffsets(0, idx - 1, 0, -deltaLength); - shiftOffsets(idx, deltaOrdinal, 0); - negativeLength += deltaLength; - } - } - structLength += deltaLength; -// nonpackedAlignedStructLength = -1; - } - - /* - * - */ - private void shiftOffsets(int index, int deltaOrdinal, int deltaOffset) { - shiftOffsets(index, components.size() - 1, deltaOrdinal, deltaOffset); - } - - /** - * - * @param startIndex the min index in the defined component arraylist - * @param endIndex the max index in the defined component arraylist - * @param deltaOrdinal - * @param deltaOffset - */ - protected void shiftOffsets(int startIndex, int endIndex, int deltaOrdinal, int deltaOffset) { - for (int i = startIndex; i <= endIndex && i < components.size(); i++) { - DataTypeComponentImpl dtc = components.get(i); - shiftOffset(dtc, deltaOrdinal, deltaOffset); - } - } - - protected DataTypeComponent getDefinedComponent(int ordinal) { - if (ordinal < 0 || ordinal >= numComponents) { - throw new IndexOutOfBoundsException(ordinal); - } - int idx = Collections.binarySearch(components, Integer.valueOf(ordinal), ordinalComparator); - if (idx >= 0) { - return components.get(idx); - } - return null; - } - - @Override - public DataTypeComponentImpl getComponent(int ordinal) { - if (ordinal < 0 || ordinal >= numComponents) { - throw new IndexOutOfBoundsException(ordinal); - } - int idx = Collections.binarySearch(components, Integer.valueOf(ordinal), ordinalComparator); - if (idx >= 0) { - return components.get(idx); - } - idx = -idx - 1; - int prevIndex = idx - 1; - int offset = computeOffset(ordinal, prevIndex); - return new DataTypeComponentImpl(DataType.DEFAULT, this, 1, ordinal, offset); - } - - protected int computeOffset(int ordinal, int prevIndex) { - int offset; - if (prevIndex < 0) { - offset = splitOffset - negativeLength + ordinal; - } - else { - DataTypeComponent prevElement = components.get(prevIndex); - int prevOrdinal = prevElement.getOrdinal(); - int endOffset = prevElement.getEndOffset(); - offset = endOffset + ordinal - prevOrdinal; - } - return offset; - } - - @Override - public int getNumComponents() { - return numComponents; - } - - @Override - public DataTypeComponentImpl insertAtOffset(int offset, DataType dataType, int length, - String newName, String comment) throws IllegalArgumentException { - if (offset < splitOffset - negativeLength || offset >= splitOffset + positiveLength) { - throw new IllegalArgumentException( - "Offset " + offset + " is not in " + getDisplayName() + "."); - } - validateDataType(dataType); - int nextOffset = offset + length; - if (offset > positiveLength) { - int deltaLength = offset - positiveLength; - numComponents += deltaLength; - positiveLength += deltaLength; - structLength += deltaLength; -// nonpackedAlignedStructLength = -1; - } - if (nextOffset < splitOffset - negativeLength) { - int deltaLength = splitOffset - nextOffset - negativeLength; - numComponents += deltaLength; - negativeLength += deltaLength; - structLength += deltaLength; -// nonpackedAlignedStructLength = -1; - } - checkAncestry(dataType); - dataType = dataType.clone(getDataTypeManager()); - - int index = Collections.binarySearch(components, Integer.valueOf(offset), offsetComparator); - - int additionalShift = 0; - if (index >= 0) { - DataTypeComponent dtc = components.get(index); - if (offset < 0) { - additionalShift = offset - dtc.getEndOffset(); - } - else { - additionalShift = offset - dtc.getOffset(); - } - } - else { - index = -index - 1; - } -// TODO: ?? - int ordinal = negativeLength + offset; - if (index > 0) { - DataTypeComponent dtc = components.get(index - 1); - ordinal = dtc.getOrdinal() + offset - dtc.getEndOffset(); - } - - DataTypeComponentImpl dtc = - new DataTypeComponentImpl(dataType, this, length, ordinal, offset, newName, comment); - dataType.addParent(this); - adjustOffsets(index, offset, 1 + additionalShift, dtc.getLength() + additionalShift); - components.add(index, dtc); - numComponents++; - notifySizeChanged(); - return dtc; - } - - @Override - public DataTypeComponent add(DataType dataType, int length, String newName, String comment) { - return addPositive(dataType, length, newName, comment); - } - - @Override - public DataTypeComponent addPositive(DataType dataType, int length, String newName, - String comment) throws IllegalArgumentException { - - validateDataType(dataType); - checkAncestry(dataType); - dataType = dataType.clone(getDataTypeManager()); -// int dtLength = dataType.getLength(); - - int offset = positiveLength; - DataTypeComponentImpl dtc = new DataTypeComponentImpl(dataType, this, length, numComponents, - offset, newName, comment); - dataType.addParent(this); - components.add(dtc); - numComponents++; - positiveLength += length; - structLength += length; -// nonpackedAlignedStructLength = -1; - notifySizeChanged(); - return dtc; - } - - @Override - public DataTypeComponent addNegative(DataType dataType, int length, String newName, - String comment) throws IllegalArgumentException { - - validateDataType(dataType); - checkAncestry(dataType); - dataType = dataType.clone(getDataTypeManager()); -// int dtLength = dataType.getLength(); - - shiftOffsets(0, numComponents - 1, 1, 0); - int offset = splitOffset - negativeLength - length; - DataTypeComponentImpl dtc = - new DataTypeComponentImpl(dataType, this, length, 0, offset, newName, comment); - dataType.addParent(this); - components.add(dtc); - numComponents++; - negativeLength += length; - structLength += length; -// nonpackedAlignedStructLength = -1; - notifySizeChanged(); - return dtc; - } - - @Override - public void setLength(int len) { - throw new UnsupportedOperationException("setLength not supported"); - } - - /** - * Increases the size of the bidirectional data type If amount is positive then the positive - * offset side will grow by the indicated amount. If amount is negative, the data type grows on - * the negative offsets side. - * - * @param amount Positive value indicates number of bytes to add to positive side. Negative - * value indicates number of bytes to add to negative side. - */ - @Override - public void growStructure(int amount) { - int absAmount; - if (amount < 0) { - absAmount = -amount; - negativeLength -= amount; - adjustOffsets(0, negativeLength, absAmount, 0); - } - else { - absAmount = amount; - positiveLength += amount; - } - numComponents += absAmount; - structLength += absAmount; -// nonpackedAlignedStructLength = -1; - notifySizeChanged(); - } - - @Override - public DataTypeComponent insert(int index, DataType dataType, int length, String newName, - String comment) { - throw new UnsupportedOperationException("BiDirectionDataType.insert() not implemented."); - } - - protected void insertAtOffset(int offset, int numBytes) { - if (offset < splitOffset - negativeLength || offset > splitOffset + positiveLength) { - throw new IllegalArgumentException("Offset " + offset + - " is not a valid insertion point in " + getDisplayName() + "."); - } - DataTypeComponent dtc = getComponentAt(offset); - int numDefinedComponents = components.size(); - int definedIndex = 0; - if (dtc == null) { - if (offset == positiveLength) { - definedIndex = numDefinedComponents; - } - else { - throw new IllegalArgumentException("Offset " + offset + - " is not a valid insertion point in " + getDisplayName() + "."); - } - } - else if (dtc.getOffset() != offset) { - throw new IllegalArgumentException("Cannot insert at offset " + offset + - " within a defined component in " + getDisplayName() + "."); - } - else { - definedIndex = Collections.binarySearch(components, Integer.valueOf(dtc.getOrdinal()), - ordinalComparator); - if (definedIndex < 0) { - definedIndex = -definedIndex - 1; - } - } - if (offset <= 0) { - shiftOffsets(0, definedIndex - 1, 0, -numBytes); - shiftOffsets(definedIndex, numDefinedComponents - 1, numBytes, 0); - negativeLength += numBytes; - } - else { - shiftOffsets(definedIndex, numDefinedComponents - 1, numBytes, numBytes); - positiveLength += numBytes; - } - numComponents += numBytes; - structLength += numBytes; -// nonpackedAlignedStructLength = -1; - notifySizeChanged(); - } - - @Override - public void deleteAtOffset(int offset) { - if (offset < splitOffset - negativeLength || offset >= splitOffset + positiveLength) { - throw new IllegalArgumentException( - "Offset " + offset + " is not in " + getDisplayName() + "."); - } - int index = Collections.binarySearch(components, Integer.valueOf(offset), offsetComparator); - - int length = 1; - if (index < 0) { - index = -index - 1; - } - else { - DataTypeComponent dtc = components.remove(index); - dtc.getDataType().removeParent(this); - length = dtc.getLength(); - } - adjustOffsets(index, offset, -1, -length); - numComponents--; - } - - @Override - public void clearAtOffset(int offset) { - if (offset < splitOffset - negativeLength || offset >= splitOffset + positiveLength) { - throw new IllegalArgumentException( - "Offset " + offset + " is not in " + getDisplayName() + "."); - } - int index = Collections.binarySearch(components, Integer.valueOf(offset), offsetComparator); - if (index >= 0) { - DataTypeComponent dtc = components.remove(index); - dtc.getDataType().removeParent(this); - int len = dtc.getLength(); - if (len > 1) { - int deltaLength = len - 1; - shiftOffsets(index, deltaLength, 0); - numComponents += deltaLength; - } - } - } - - @Override - public boolean isEquivalent(DataType dataType) { - if (dataType == this) { - return true; - } - if (dataType == null) { - return false; - } - - if (dataType instanceof BiDirectionStructure) { - BiDirectionStructure biDir = (BiDirectionStructure) dataType; - if ((splitOffset != biDir.getSplitOffset()) || - (negativeLength != biDir.getNegativeLength()) || - (positiveLength != biDir.getPositiveLength()) || - (getLength() != biDir.getLength())) { - return false; - } - DataTypeComponent[] myComps = getDefinedComponents(); - DataTypeComponent[] otherComps = biDir.getDefinedComponents(); - if (myComps.length != otherComps.length) { - return false; - } - for (int i = 0; i < myComps.length; i++) { - if (!myComps[i].isEquivalent(otherComps[i])) { - return false; - } - } - return true; - } - return false; - } - - @Override - public void dataTypeSizeChanged(DataType dt) { - // ignore - } - - @Override - public void dataTypeAlignmentChanged(DataType dt) { - // ignore - } - - @Override - public abstract BiDirectionDataType clone(DataTypeManager dtm); - - @Override - public void clearComponent(int ordinal) { - if (ordinal < 0 || ordinal >= numComponents) { - throw new IndexOutOfBoundsException(ordinal); - } - int index = - Collections.binarySearch(components, Integer.valueOf(ordinal), ordinalComparator); - if (index >= 0) { - DataTypeComponent dtc = components.remove(index); - dtc.getDataType().removeParent(this); - int len = dtc.getLength(); - if (len > 1) { - int deltaLength = len - 1; - shiftOffsets(index, deltaLength, 0); - numComponents += deltaLength; - } - } - } - - public void replaceWith(Structure struct) { - throw new UnsupportedOperationException( - "BiDirectionDataType.replaceWith() not implemented."); - } - - @Override - public void dataTypeDeleted(DataType dt) { - throw new UnsupportedOperationException( - "BiDirectionDataType.dataTypeDeleted() not implemented."); - } - - @Override - public void dataTypeReplaced(DataType oldDt, DataType newDt) { - throw new UnsupportedOperationException( - "BiDirectionDataType.dataTypeReplaced() not implemented."); - } - - @Override - public DataTypeComponent[] getDefinedComponents() { - return components.toArray(new DataTypeComponent[components.size()]); - } - - @Override - public DataTypeComponent[] getComponents() { - DataTypeComponent[] comps = new DataTypeComponent[numComponents]; - for (int i = 0; i < comps.length; i++) { - comps[i] = getComponent(i); - } - return comps; - } - - @Override - public DataTypeComponent replace(int index, DataType dataType, int length, String newName, - String comment) throws IndexOutOfBoundsException, IllegalArgumentException { - if (index < 0 || index >= numComponents) { - throw new IndexOutOfBoundsException(index); - } - validateDataType(dataType); - checkAncestry(dataType); - dataType = dataType.clone(getDataTypeManager()); - DataTypeComponent origDtc = getComponent(index); - return replace(origDtc, dataType, length, newName, comment); - } - - @Override - public DataTypeComponent replaceAtOffset(int offset, DataType dataType, int length, - String newName, String comment) throws IllegalArgumentException { - if (offset < splitOffset - negativeLength || offset >= splitOffset + positiveLength) { - throw new IllegalArgumentException( - "Offset " + offset + " is not in " + getDisplayName() + "."); - } - validateDataType(dataType); - checkAncestry(dataType); - dataType = dataType.clone(getDataTypeManager()); - DataTypeComponent origDtc = getComponentAt(offset); - DataTypeComponent newDtc = replace(origDtc, dataType, length, newName, comment); - return newDtc; - } - - /** - * Replace the indicated component with a new component containing the specified data type. - * - * @param origDtc the original data type component in this structure. - * @param dataType the data type of the new component - * @param length the length of the new component - * @param newName the field name of the new component - * @param comment the comment for the new component - * @return the new component or null if the new component couldn't fit. - * @throws IllegalArgumentException if the dataType.getLength() is positive and does not match - * the given length parameter. - * @throws IllegalArgumentException if the specified data type is not allowed to replace a - * component in this composite data type. For example, suppose dt1 contains dt2. - * Therefore it is not valid to replace a dt2 component with dt1 since this would - * cause a cyclic dependency. - */ - private DataTypeComponent replace(DataTypeComponent origDtc, DataType dataType, int length, - String newName, String comment) { - - int ordinal = origDtc.getOrdinal(); - int newOffset = origDtc.getOffset(); - int dtcLength = origDtc.getLength(); - int bytesNeeded = length - dtcLength; - int deltaOrdinal = -bytesNeeded; - if (bytesNeeded > 0) { - int bytesAvailable = getNumUndefinedBytes(ordinal + 1); - if (bytesAvailable < bytesNeeded) { -// throw new IllegalArgumentException("Not enough undefined bytes."); - deltaOrdinal = -bytesAvailable; - length -= (bytesNeeded - bytesAvailable); - } - } - origDtc.getDataType().removeParent(this); - DataTypeComponentImpl newDtc = - new DataTypeComponentImpl(dataType, this, length, ordinal, newOffset, newName, comment); - dataType.addParent(this); - int index = - Collections.binarySearch(components, Integer.valueOf(ordinal), ordinalComparator); - if (index < 0) { - index = -index - 1; - } - else { - components.remove(index); - } - if (deltaOrdinal != 0) { - adjustOffsets(index, newOffset, deltaOrdinal, 0); - } - components.add(index, newDtc); - if (deltaOrdinal != 0) { - numComponents += deltaOrdinal; - } - return newDtc; - } - -} - -class OffsetComparator implements Comparator { - - @Override - public int compare(Object o1, Object o2) { - if (o1 instanceof Integer) { - return -compare(o2, o1); - } - DataTypeComponent dtc = (DataTypeComponent) o1; - int offset = ((Integer) o2).intValue(); - if (offset < dtc.getOffset()) { - return 1; - } - else if (offset > dtc.getEndOffset()) { - return -1; - } - return 0; - } - -} - -class OrdinalComparator implements Comparator { - - @Override - public int compare(Object o1, Object o2) { - if (o1 instanceof Integer) { - return -compare(o2, o1); - } - DataTypeComponent dtc = (DataTypeComponent) o1; - int ordinal = ((Integer) o2).intValue(); - return dtc.getOrdinal() - ordinal; - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/BiDirectionStructure.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/BiDirectionStructure.java deleted file mode 100644 index b5b62cc6a5..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/BiDirectionStructure.java +++ /dev/null @@ -1,44 +0,0 @@ -/* ### - * 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.stackeditor; - -import ghidra.program.model.data.*; - -public interface BiDirectionStructure extends Structure { - - /** - * Get the length of this DataType in the negative direction. - * @return the length of this DataType in the negative direction. - */ - public abstract int getNegativeLength(); - - /** - * Get the length of this DataType in the positive direction. - * @return the length of this DataType in the positive direction. - */ - public abstract int getPositiveLength(); - - /** - * Get the component offset which represents the division point - * between the positive and negative halves of the structure. - * @return split offset - */ - public abstract int getSplitOffset(); - - public DataTypeComponent addNegative(DataType dataType, int length, String name, String comment); - - public DataTypeComponent addPositive(DataType dataType, int length, String name, String comment); -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java index 3d1d6eea3b..a4faae0234 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorModel.java @@ -17,8 +17,6 @@ package ghidra.app.plugin.core.stackeditor; import java.util.*; -import javax.swing.JOptionPane; - import docking.widgets.OptionDialog; import docking.widgets.fieldpanel.support.FieldRange; import docking.widgets.fieldpanel.support.FieldSelection; @@ -32,7 +30,9 @@ import docking.widgets.fieldpanel.support.FieldSelection; * When edit actions occur and there is a selection, the listener's are notified * of the new selection via the listener's overrideSelection method. */ -import ghidra.app.plugin.core.compositeeditor.*; +import ghidra.app.plugin.core.compositeeditor.CompositeEditorModel; +import ghidra.app.plugin.core.compositeeditor.CompositeViewerDataTypeManager; +import ghidra.app.plugin.core.stackeditor.StackFrameDataType.StackComponentWrapper; import ghidra.app.util.datatype.EmptyCompositeException; import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.PluginTool; @@ -57,7 +57,8 @@ public class StackEditorModel extends CompositeEditorModel { private static final int MAX_LOCAL_SIZE = Integer.MAX_VALUE; private static final int MAX_PARAM_SIZE = Integer.MAX_VALUE; - private StackFrame originalStack; + private Function function; + private StackFrameDataType originalStackFrameDataType; private boolean stackChangedExternally; @@ -91,16 +92,23 @@ public class StackEditorModel extends CompositeEditorModel { return false; } - void stackChangedExternally(boolean changed) { - stackChangedExternally = changed; + @Override + protected boolean usesAlignedLengthComponents() { + // NOTE: It is assumed that aligned-length is not used by stack variables + return false; } - void load(Function function) { - originalStack = function.getStackFrame(); - ProgramBasedDataTypeManager dtm = function.getProgram().getDataTypeManager(); - StackFrameDataType stackFrameDataType = new StackFrameDataType(originalStack, dtm); - stackFrameDataType.setCategoryPath(dtm.getRootCategory().getCategoryPath()); - load(stackFrameDataType); + void stackChangedExternally(boolean changed) { + stackChangedExternally = changed; + if (changed) { + setStatus("Stack may have been changed externally -- data may be stale."); + } + } + + void load(Function func) { + function = func; + originalStackFrameDataType = new StackFrameDataType(function); + load(originalStackFrameDataType); } @Override @@ -110,18 +118,27 @@ public class StackEditorModel extends CompositeEditorModel { } @Override - protected void createViewCompositeFromOriginalComposite(StackFrameDataType original) { + protected void createViewCompositeFromOriginalComposite() { if (viewDTM != null) { viewDTM.close(); viewDTM = null; } - // Use temporary standalone view datatype manager which will not manage the viewComposite + // Establish editor's datatype manager which will manage datatype dependencies. viewDTM = new CompositeViewerDataTypeManager<>(originalDTM.getName(), originalDTM); - // NOTE: StackFrameDataType cannot be resolved but all of its element datatypes must be - viewComposite = original.copy(viewDTM); + // Create a copy of the original stack frame datatype and force the resolving of its + // datatype dependencies. A round-about approach is used since the StackFrameDataType + // itself cannot be resolved and cannot be treated in the same fashion as a normal + // Structure edit. It relies on wrapping a normal structure to serve as a proxy of sorts + // for the purpose of managing datatype dependencies. It is only through the use of + // a StructureDB that component datatypes are forced to be resolved into the viewDTM. + // NOTE: Since the StackEditorDataTypeManager keeps a single transaction open unused + // datatype pruning is never performed. + viewComposite = originalComposite.copy(originalDTM); + originalComposite.resolveWrappedComposite(viewDTM); + originalComposite = originalStackFrameDataType; // use true original } @Override @@ -131,18 +148,15 @@ public class StackEditorModel extends CompositeEditorModel { @Override public boolean updateAndCheckChangeState() { - if (originalIsChanging) { - return false; - } StackFrameDataType sfdt = viewComposite; int editReturnAddressOffset = sfdt.getReturnAddressOffset(); int editLocalSize = sfdt.getLocalSize(); int editParamOffset = sfdt.getParameterOffset(); int editParamSize = sfdt.getParameterSize(); - int stackReturnAddressOffset = originalStack.getReturnAddressOffset(); - int stackLocalSize = originalStack.getLocalSize(); - int stackParamOffset = originalStack.getParameterOffset(); - int stackParamSize = originalStack.getParameterSize(); + int stackReturnAddressOffset = originalStackFrameDataType.getReturnAddressOffset(); + int stackLocalSize = originalStackFrameDataType.getLocalSize(); + int stackParamOffset = originalStackFrameDataType.getParameterOffset(); + int stackParamSize = originalStackFrameDataType.getParameterSize(); hasChanges = (editReturnAddressOffset != stackReturnAddressOffset) || (editLocalSize != stackLocalSize) || (editParamOffset != stackParamOffset) || (editParamSize != stackParamSize) || super.updateAndCheckChangeState(); @@ -185,7 +199,7 @@ public class StackEditorModel extends CompositeEditorModel { (rowIndex < 0) || (columnIndex < 0) || (columnIndex >= getColumnCount())) { return ""; } - DataTypeComponent element = viewComposite.getComponent(rowIndex); + StackComponentWrapper element = viewComposite.getComponent(rowIndex); DataType dt; int dtLen; switch (columnIndex) { @@ -214,10 +228,6 @@ public class StackEditorModel extends CompositeEditorModel { if (fieldName == null) { fieldName = ""; } -// if ((fieldName.length() == 0) -// && (element.getOffset() == ((StackFrameDataType)viewComposite).getReturnAddressOffset())) { -// return ""; -// } return fieldName; case COMMENT: return element.getComment(); @@ -226,13 +236,13 @@ public class StackEditorModel extends CompositeEditorModel { } } - private String getFieldNameAtRow(int rowIndex, StackFrameDataType stackFrameDataType) { - DataTypeComponent dataType = stackFrameDataType.getComponent(rowIndex); - String fieldName = dataType.getFieldName(); + private String getFieldNameAtRow(int rowIndex, StackFrameDataType stackDt) { + StackComponentWrapper stackDtc = stackDt.getComponent(rowIndex); + String fieldName = stackDtc.getFieldName(); if (fieldName == null) { // If the component is a defined stack variable with no name, use default name. - if (stackFrameDataType.isStackVariable(rowIndex)) { - fieldName = stackFrameDataType.getDefaultName(dataType); + if (stackDt.isStackVariable(rowIndex)) { + fieldName = stackDt.getDefaultName(stackDtc); } } return fieldName; @@ -249,23 +259,12 @@ public class StackEditorModel extends CompositeEditorModel { public void setValueAt(Object aValue, int rowIndex, int modelColumnIndex) { try { settingValueAt = true; - OffsetPairs offsetSelection = getRelOffsetSelection(); - Object originalValue = getValueAt(rowIndex, modelColumnIndex); if (SystemUtilities.isEqual(originalValue, aValue)) { return; } - - if (fieldEdited(aValue, rowIndex, modelColumnIndex)) { - if (modelColumnIndex == OFFSET) { - int svOffset = Integer.decode((String) aValue).intValue(); - DataTypeComponent dtc = - (viewComposite).getComponentAt(svOffset); - offsetSelection = new OffsetPairs(); - offsetSelection.addPair(svOffset, dtc.getEndOffset()); - } - } - setRelOffsetSelection(offsetSelection); + fieldEdited(aValue, rowIndex, modelColumnIndex); + setSelection(new int[] { rowIndex }); } finally { settingValueAt = false; @@ -292,9 +291,6 @@ public class StackEditorModel extends CompositeEditorModel { setComponentOffset(rowIndex, (String) value); break; case DATATYPE: - if (value instanceof StackPieceDataType) { - return true; // no change - } setComponentDataType(rowIndex, value); break; case NAME: @@ -424,10 +420,6 @@ public class StackEditorModel extends CompositeEditorModel { return viewComposite.getDefinedComponentAtOffset(offset) != null; } - StackFrame getOriginalStack() { - return originalStack; - } - StackFrameDataType getEditorStack() { return viewComposite; } @@ -603,10 +595,13 @@ public class StackEditorModel extends CompositeEditorModel { */ @Override public boolean isAddAllowed(int currentIndex, DataType dataType) { + + if (currentIndex < 0 || currentIndex >= getRowCount()) { + return false; + } + try { - if (currentIndex < 0 || currentIndex >= getRowCount()) { - return false; - } + checkIsAllowableDataType(dataType); } catch (InvalidDataTypeException e) { @@ -627,18 +622,6 @@ public class StackEditorModel extends CompositeEditorModel { newLength = compDt.getLength(); } int offset = comp.getOffset(); -// TODO: not sure we need to prevent creating local variables in 'save' area, -// since doing so just leads to confusion when using stack frame editor -// if (((StackFrameDataType) viewComposite).growsNegative()) { -// if (offset >= 0 && offset < getParameterOffset()) { -// return false; -// } -// } -// else { -// if (offset < 0 && offset > getParameterOffset()) { -// return false; -// } -// } int maxBytes = viewComposite.getMaxLength(offset); if (newLength > maxBytes) { return false; @@ -660,7 +643,8 @@ public class StackEditorModel extends CompositeEditorModel { if (index < 0 || index >= viewComposite.getNumComponents()) { return false; } - return viewComposite.getDefinedComponentAtOrdinal(index) != null; + StackComponentWrapper dtc = viewComposite.getDefinedComponentAtOrdinal(index); + return dtc != null; } @Override @@ -683,17 +667,8 @@ public class StackEditorModel extends CompositeEditorModel { return false; } int paramOffset = getParameterOffset(); - if (paramOffset >= 0) { - // grows negative - if (startOffset < paramOffset && endOffset >= paramOffset) { - return false; - } - } - else { - // grows positive - if (startOffset <= paramOffset && endOffset > paramOffset) { - return false; - } + if (startOffset < paramOffset && endOffset >= paramOffset) { + return false; } return true; } @@ -723,59 +698,6 @@ public class StackEditorModel extends CompositeEditorModel { return true; } - private void adjustComponents(DataType dataType) { - DataTypeComponent[] comps = viewComposite.getDefinedComponents(); - String msg = ""; - for (DataTypeComponent component : comps) { - DataType compDt = component.getDataType(); - if (compDt == dataType) { - int len = compDt.getLength(); - if (len <= 0) { - len = component.getLength(); - } - try { - viewComposite.replace(component.getOrdinal(), compDt, len, - component.getFieldName(), - component.getComment()); - } - catch (IllegalArgumentException e) { - msg += "Adjusting variable at offset " + - getHexString(component.getOffset(), true) + ". " + e.getMessage() + "\n"; - } - } - } - if (msg.length() > 0) { - JOptionPane.showMessageDialog(provider.getComponent(), msg, - "Stack Editor Adjustment Warning", JOptionPane.WARNING_MESSAGE); - } - } - - private void replaceComponents(DataType oldDataType, DataType newDataType) { - DataTypeComponent[] comps = viewComposite.getDefinedComponents(); - String msg = ""; - for (DataTypeComponent component : comps) { - DataType compDt = component.getDataType(); - if (compDt == oldDataType) { - int len = newDataType.getLength(); - if (len <= 0) { - len = component.getLength(); - } - try { - viewComposite.replace(component.getOrdinal(), newDataType, len, - component.getFieldName(), component.getComment()); - } - catch (IllegalArgumentException e) { - msg += "Replacing variable at offset " + - getHexString(component.getOffset(), true) + ". " + e.getMessage() + "\n"; - } - } - } - if (msg.length() > 0) { - JOptionPane.showMessageDialog(provider.getComponent(), msg, - "Stack Editor Replacement Warning", JOptionPane.WARNING_MESSAGE); - } - } - @Override public void setComponentDataTypeInstance(int index, DataType dt, int length) throws UsrException { @@ -800,15 +722,14 @@ public class StackEditorModel extends CompositeEditorModel { // prevent user names that are default values, unless the value is the original name String nameInEditor = (String) getValueAt(rowIndex, NAME); - StackFrameDataType stackFrameDataType = viewComposite; - if (stackFrameDataType.isDefaultName(newName) && !isOriginalFieldName(newName, rowIndex)) { + if (viewComposite.isDefaultName(newName) && !isOriginalFieldName(newName, rowIndex)) { if (Objects.equals(nameInEditor, newName)) { return false; // same as current name in the table; do nothing } throw new InvalidNameException("Cannot set a stack variable name to a default value"); } - if (stackFrameDataType.setName(rowIndex, newName)) { + if (viewComposite.setName(rowIndex, newName)) { updateAndCheckChangeState(); fireTableCellUpdated(rowIndex, getNameColumn()); notifyCompositeChanged(); @@ -819,8 +740,7 @@ public class StackEditorModel extends CompositeEditorModel { /** Gets the original field name within the parent data type for a given row in the editor */ private boolean isOriginalFieldName(String testName, int rowIndex) { - StackFrameDataType dataType = getOriginalComposite(); - String fieldName = getFieldNameAtRow(rowIndex, dataType); + String fieldName = getFieldNameAtRow(rowIndex, originalStackFrameDataType); return SystemUtilities.isEqual(fieldName, testName); } @@ -857,6 +777,7 @@ public class StackEditorModel extends CompositeEditorModel { */ @Override public DataTypeComponent add(int index, DataType dt) throws UsrException { + // NOTE: Unused method return replace(index, dt); } @@ -909,16 +830,14 @@ public class StackEditorModel extends CompositeEditorModel { if (!isValidName() || !hasChanges()) { return false; } - StackFrame original = getOriginalStack(); // FIXME: Not Needed - use originalStack - Function function = original.getFunction(); - StackFrameDataType edited = getEditorStack(); - Variable[] newVars = edited.getStackVariables(); + Variable[] newVars = viewComposite.getStackVariables(); List newVarsList = Arrays.asList(newVars); Collections.sort(newVarsList, StackVariableComparator.get()); // sort for use with getVariableContaining - original.setLocalSize(edited.getLocalSize()); - original.setReturnAddressOffset(edited.getReturnAddressOffset()); + StackFrame functionStackFrame = function.getStackFrame(); + functionStackFrame.setLocalSize(viewComposite.getLocalSize()); + functionStackFrame.setReturnAddressOffset(viewComposite.getReturnAddressOffset()); // first-pass: remove deleted params from end of param list if possible // to avoid custom storage enablement @@ -959,7 +878,7 @@ public class StackEditorModel extends CompositeEditorModel { Variable newSv = null; try { DataType dt = originalDTM.resolve(sv.getDataType(), null); - Variable var = original.getVariableContaining(sv.getStackOffset()); + Variable var = functionStackFrame.getVariableContaining(sv.getStackOffset()); // TODO: Handle case where new size is smaller but stack alignment will prevent variable shuffle on setDataType - could be problamatic if (var != null && var.getStackOffset() == sv.getStackOffset() && var.getLength() == sv.getLength()) { @@ -972,16 +891,16 @@ public class StackEditorModel extends CompositeEditorModel { } } else { - if (original.isParameterOffset(sv.getStackOffset()) || + if (functionStackFrame.isParameterOffset(sv.getStackOffset()) || (var instanceof Parameter)) { // about to make param change - must enable custom storage - original.getFunction().setCustomVariableStorage(true); + functionStackFrame.getFunction().setCustomVariableStorage(true); } if (var != null) { - original.clearVariable(var.getStackOffset()); + functionStackFrame.clearVariable(var.getStackOffset()); } - newSv = original.createVariable(sv.getName(), sv.getStackOffset(), dt, - SourceType.USER_DEFINED); + newSv = functionStackFrame.createVariable(sv.getName(), sv.getStackOffset(), + dt, SourceType.USER_DEFINED); } newSv.setComment(sv.getComment()); } @@ -1002,7 +921,7 @@ public class StackEditorModel extends CompositeEditorModel { newSv.setComment(comment); } } - load(new StackFrameDataType(original, originalDTM)); + load(function); clearStatus(); return true; } @@ -1081,14 +1000,6 @@ public class StackEditorModel extends CompositeEditorModel { return false; } - public DataTypeComponent replace(DataType dataType) throws UsrException { - int rowIndex = getMinIndexSelected(); - if (rowIndex < 0) { - throw new UsrException("A component must be selected."); - } - return replace(rowIndex, dataType); - } - private DataTypeComponent replace(int index, DataType dataType) throws UsrException { try { DataTypeInstance dti = getDropDataType(index, dataType); @@ -1102,11 +1013,10 @@ public class StackEditorModel extends CompositeEditorModel { @Override public DataTypeComponent replace(int index, DataType dt, int dtLength) throws UsrException { - OffsetPairs offsetSelection = getRelOffsetSelection(); fieldEdited( DataTypeInstance.getDataTypeInstance(dt, dtLength, usesAlignedLengthComponents()), index, getDataTypeColumn()); - setRelOffsetSelection(offsetSelection); + setSelection(new int[] { index }); return getComponent(index); } @@ -1135,14 +1045,16 @@ public class StackEditorModel extends CompositeEditorModel { @Override public void restored(DataTypeManager dataTypeManager) { + functionChanged(true); + } + + void functionChanged(boolean isRestore) { - StackFrameDataType sfdt = getOriginalComposite(); - Function function = sfdt.getFunction(); if (function.isDeleted()) { - // Cancel Editor. - provider.dispose(); + // Close the Editor. PluginTool tool = ((StackEditorProvider) provider).getPlugin().getTool(); tool.setStatusInfo("Stack Editor was closed for " + provider.getName()); + provider.dispose(); return; } @@ -1152,9 +1064,9 @@ public class StackEditorModel extends CompositeEditorModel { if (hasChanges) { // The user has modified the structure so prompt for whether or // not to reload the structure. - String question = "The program \"dtm.getName()\" has been restored.\n" + "\"" + - currentName + "\" may have changed outside the editor.\n" + - "Discard edits and reload the Stack Editor?"; + String text = isRestore ? "may have " : ""; + String question = "The function \"" + currentName + "\" " + text + + "changed outside the editor.\n" + "Discard edits and reload the Stack Editor?"; String title = "Reload Stack Editor?"; int response = OptionDialog .showYesNoDialogWithNoAsDefaultButton(provider.getComponent(), title, question); @@ -1166,10 +1078,83 @@ public class StackEditorModel extends CompositeEditorModel { load(function); } else { + stackChangedExternally(true); refresh(); } } + @Override + public void dataTypeRemoved(DataTypeManager dataTypeManager, DataTypePath path) { + + if (dataTypeManager != originalDTM) { + throw new AssertException("Listener only supports original DTM"); + } + if (!isLoaded()) { + return; + } + + DataType dataType = viewDTM.getDataType(path.getCategoryPath(), path.getDataTypeName()); + if (dataType == null || !viewDTM.isViewDataTypeFromOriginalDTM(dataType)) { + return; + } + + OffsetPairs offsetSelection = getRelOffsetSelection(); + viewDTM.remove(dataType, TaskMonitor.DUMMY); + fireTableDataChanged(); + componentDataChanged(); + setRelOffsetSelection(offsetSelection); + } + + @Override + public void dataTypeRenamed(DataTypeManager dataTypeManager, DataTypePath oldPath, + DataTypePath newPath) { + dataTypeMoved(dataTypeManager, oldPath, newPath); + } + + @Override + public void dataTypeMoved(DataTypeManager dataTypeManager, DataTypePath oldPath, + DataTypePath newPath) { + + if (dataTypeManager != originalDTM) { + throw new AssertException("Listener only supports original DTM"); + } + + if (!isLoaded()) { + return; + } + + if (oldPath.getDataTypeName().equals(newPath.getDataTypeName())) { + return; + } + + // Check for managed datatype changing + DataType originalDt = originalDTM.getDataType(newPath); + if (!(originalDt instanceof DatabaseObject)) { + return; + } + DataType dt = viewDTM.findMyDataTypeFromOriginalID(originalDTM.getID(originalDt)); + if (dt == null) { + return; + } + + OffsetPairs offsetSelection = getRelOffsetSelection(); + try { + dt.setName(newPath.getDataTypeName()); + + CategoryPath newCategoryPath = newPath.getCategoryPath(); + CategoryPath oldCategoryPath = oldPath.getCategoryPath(); + if (!newCategoryPath.equals(oldCategoryPath)) { + dt.setCategoryPath(newCategoryPath); + } + } + catch (InvalidNameException | DuplicateNameException e) { + Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); + } + fireTableDataChanged(); + componentDataChanged(); + setRelOffsetSelection(offsetSelection); + } + @Override public void dataTypeChanged(DataTypeManager dataTypeManager, DataTypePath path) { if (dataTypeManager != originalDTM) { @@ -1178,65 +1163,25 @@ public class StackEditorModel extends CompositeEditorModel { if (!isLoaded()) { return; } - DataType dataType = dataTypeManager.getDataType(path); + + DataType changedDt = originalDTM.getDataType(path); + if (!(changedDt instanceof DatabaseObject)) { + return; + } + DataType viewDt = viewDTM.findMyDataTypeFromOriginalID(originalDTM.getID(changedDt)); + if (viewDt == null) { + return; + } + OffsetPairs offsetSelection = getRelOffsetSelection(); - adjustComponents(dataType); - fireTableDataChanged(); - componentDataChanged(); - setRelOffsetSelection(offsetSelection); - } - - @Override - public void dataTypeMoved(DataTypeManager dataTypeManager, DataTypePath oldPath, - DataTypePath newPath) { - if (dataTypeManager != originalDTM) { - throw new AssertException("Listener only supports original DTM"); + try { + viewDTM.replaceDataType(viewDt, changedDt, true); + viewComposite.checkForStackGrowth(); } - if (!isLoaded()) { - return; + catch (DataTypeDependencyException e) { + throw new AssertException(e); } - if (originalDataTypePath != null && - originalDataTypePath.getDataTypeName().equals(newPath.getDataTypeName()) && - originalDataTypePath.getCategoryPath().equals(oldPath.getCategoryPath())) { - originalDataTypePath = newPath; - compositeInfoChanged(); - } - } - - @Override - public void dataTypeRemoved(DataTypeManager dataTypeManager, DataTypePath path) { - if (dataTypeManager != originalDTM) { - throw new AssertException("Listener only supports original DTM"); - } - if (!isLoaded()) { - return; - } - OffsetPairs offsetSelection = getRelOffsetSelection(); - DataType dataType = dataTypeManager.getDataType(path); - replaceComponents(dataType, DataType.DEFAULT); - fireTableDataChanged(); - componentDataChanged(); - setRelOffsetSelection(offsetSelection); - } - - @Override - public void dataTypeRenamed(DataTypeManager dataTypeManager, DataTypePath oldPath, - DataTypePath newPath) { - if (dataTypeManager != originalDTM) { - throw new AssertException("Listener only supports original DTM"); - } - if (!isLoaded()) { - return; - } - - DataTypePath originalPath = getOriginalDataTypePath(); - if (originalPath == null || !oldPath.equals(originalPath)) { - return; - } - - // Don't try to actually rename, since we shouldn't get name change on a - // fabricated stack data type. - OffsetPairs offsetSelection = getRelOffsetSelection(); + compositeInfoChanged(); // size info may have changed fireTableDataChanged(); componentDataChanged(); setRelOffsetSelection(offsetSelection); @@ -1251,9 +1196,21 @@ public class StackEditorModel extends CompositeEditorModel { if (!isLoaded()) { return; } - DataType oldDataType = viewDTM.getDataType(oldPath); + + DataType dt = viewDTM.getDataType(oldPath); + if (dt == null || !viewDTM.isViewDataTypeFromOriginalDTM(dt)) { + return; + } + OffsetPairs offsetSelection = getRelOffsetSelection(); - replaceComponents(oldDataType, newDataType); + try { + viewDTM.replaceDataType(dt, newDataType, true); + viewComposite.checkForStackGrowth(); + } + catch (DataTypeDependencyException e) { + throw new AssertException(e); + } + compositeInfoChanged(); // size info may have changed fireTableDataChanged(); componentDataChanged(); setRelOffsetSelection(offsetSelection); @@ -1325,54 +1282,56 @@ public class StackEditorModel extends CompositeEditorModel { //************************************************************************** //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv - @Override - public DataTypeInstance validateComponentDataType(int index, String dtString) - throws CancelledException, UsrException { - DataType dt = null; - String dtName = ""; - dtString = DataTypeHelper.stripWhiteSpace(dtString); - if (index < getNumComponents()) { - DataTypeComponent element = viewComposite.getComponent(index); - dt = element.getDataType(); - dtName = dt.getDisplayName(); - if (dtString.equals(dtName)) { - return DataTypeInstance.getDataTypeInstance(element.getDataType(), - element.getLength(), usesAlignedLengthComponents()); - } - } - - DataType newDt = DataTypeHelper.parseDataType(index, dtString, this, originalDTM, - provider.getDtmService()); - - if (newDt == null) { - if (dt != null) { - throw new UsrException("No data type was specified."); - } - throw new CancelledException(); - } - - int newLength = newDt.getLength(); - - checkIsAllowableDataType(newDt); - newDt = resolveDataType(newDt, viewDTM, null); - int maxLength = getMaxReplaceLength(index); - if (newLength <= 0) { - throw new UsrException("Can't currently add this data type--not enough space."); - } - if (maxLength > 0 && newLength > maxLength) { - throw new UsrException(newDt.getDisplayName() + " doesn't fit."); - } - return DataTypeInstance.getDataTypeInstance(newDt, newLength, - usesAlignedLengthComponents()); - } +// @Override +// public DataTypeInstance validateComponentDataType(int index, String dtString) +// throws CancelledException, UsrException { +// DataType dt = null; +// String dtName = ""; +// dtString = DataTypeHelper.stripWhiteSpace(dtString); +// if (index < getNumComponents()) { +// DataTypeComponent element = viewComposite.getComponent(index); +// dt = element.getDataType(); +// dtName = dt.getDisplayName(); +// if (dtString.equals(dtName)) { +// return DataTypeInstance.getDataTypeInstance(element.getDataType(), +// element.getLength(), usesAlignedLengthComponents()); +// } +// } +// +// DataType newDt = DataTypeHelper.parseDataType(index, dtString, this, originalDTM, +// provider.getDtmService()); +// +// if (newDt == null) { +// if (dt != null) { +// throw new UsrException("No data type was specified."); +// } +// throw new CancelledException(); +// } +// +// int newLength = newDt.getLength(); +// +// checkIsAllowableDataType(newDt); +// newDt = resolveDataType(newDt, viewDTM, null); +// int maxLength = getMaxReplaceLength(index); +// if (newLength <= 0) { +// throw new UsrException("Can't currently add this data type--not enough space."); +// } +// if (maxLength > 0 && newLength > maxLength) { +// throw new UsrException(newDt.getDisplayName() + " doesn't fit."); +// } +// return DataTypeInstance.getDataTypeInstance(newDt, newLength, +// usesAlignedLengthComponents()); +// } @Override protected void deleteComponent(int rowIndex) { viewComposite.delete(rowIndex); + compositeInfoChanged(); // info may have changed + fireTableDataChanged(); } @Override - public DataTypeComponent getComponent(int rowIndex) { + public StackComponentWrapper getComponent(int rowIndex) { if (viewComposite == null) { return null; } 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 d097032cab..bfa7af5ec1 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,16 +23,13 @@ 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.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; -import ghidra.program.model.symbol.SymbolType; import ghidra.program.util.ProgramChangeRecord; import ghidra.program.util.ProgramEvent; -import ghidra.util.InvalidNameException; -import ghidra.util.Msg; +import ghidra.util.task.SwingUpdateManager; /** * Editor for a Function Stack. @@ -45,6 +42,31 @@ public class StackEditorProvider private Function function; private StackEditorModel stackModel; + boolean scheduleRefreshName = false; + boolean scheduleReload = false; + + /** + * Delay model update caused by Program change events. + */ + SwingUpdateManager delayedUpdateMgr = new SwingUpdateManager(200, 200, () -> { + try { + if (function.isDeleted()) { + stackModel.functionChanged(false); + return; + } + if (scheduleRefreshName) { + updateTitle(); + } + if (scheduleReload) { + stackModel.functionChanged(false); + } + } + finally { + scheduleRefreshName = false; + scheduleReload = false; + } + }); + public StackEditorProvider(Plugin plugin, Function function) { super(plugin); this.program = function.getProgram(); @@ -56,15 +78,22 @@ public class StackEditorProvider initializeActions(); editorPanel = new StackEditorPanel(program, stackModel, this); - setTitle(getName() + " - " + getProviderSubTitle(function)); + updateTitle(); plugin.getTool().addComponentProvider(this, true); addActionsToTool(); editorPanel.getTable().requestFocus(); } + @Override + protected void updateTitle() { + setTabText(function.getName()); + setTitle(getName() + " - " + getProviderSubTitle(function)); + } + @Override public void dispose() { + delayedUpdateMgr.dispose(); program.removeListener(this); super.dispose(); } @@ -84,6 +113,11 @@ public class StackEditorProvider return "Stack Editor"; } + @Override + protected String getDisplayName() { + return "stack frame: " + function.getName(); + } + @Override public String getHelpName() { return "Stack_Editor"; @@ -147,32 +181,6 @@ public class StackEditorProvider return actionMgr.getAllActions(); } - private void refreshName() { - StackFrameDataType origDt = stackModel.getOriginalComposite(); - StackFrameDataType viewDt = stackModel.getEditorStack(); - String oldName = origDt.getName(); - String newName = function.getName(); - if (oldName.equals(newName)) { - return; - } - - setTitle("Stack Editor: " + newName); - try { - origDt.setName(newName); - if (viewDt.getName().equals(oldName)) { - viewDt.setName(newName); - } - } - catch (InvalidNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - - CategoryPath oldCategoryPath = origDt.getCategoryPath(); - DataTypePath oldDtPath = new DataTypePath(oldCategoryPath, oldName); - DataTypePath newDtPath = new DataTypePath(oldCategoryPath, newName); - stackModel.dataTypeRenamed(stackModel.getOriginalDataTypeManager(), oldDtPath, newDtPath); - } - @Override public void domainObjectChanged(DomainObjectChangedEvent event) { if (!isVisible()) { @@ -181,35 +189,40 @@ public class StackEditorProvider int recordCount = event.numRecords(); for (int i = 0; i < recordCount; i++) { + DomainObjectChangeRecord rec = event.getChangeRecord(i); EventType eventType = rec.getEventType(); - if (eventType == DomainObjectEvent.RESTORED) { - refreshName(); - // NOTE: editorPanel should be notified of restored datatype manager via the - // CompositeViewerModel's DataTypeManagerChangeListener restored method - return; + + // NOTE: RESTORED event can be ignored here since the model will be notified + // of restored datatype manager via the CompositeViewerModel's + // DataTypeManagerChangeListener restored method. + + if (eventType == DomainObjectEvent.FILE_CHANGED) { + scheduleRefreshName = true; + delayedUpdateMgr.updateLater(); + continue; } if (eventType instanceof ProgramEvent type) { switch (type) { case FUNCTION_REMOVED: Function func = (Function) ((ProgramChangeRecord) rec).getObject(); if (func == function) { - this.dispose(); + // Close the Editor. tool.setStatusInfo("Stack Editor was closed for " + getName()); + dispose(); + return; } - return; + break; case SYMBOL_RENAMED: case SYMBOL_DATA_CHANGED: Symbol sym = (Symbol) ((ProgramChangeRecord) rec).getObject(); - SymbolType symType = sym.getSymbolType(); - if (symType == SymbolType.LABEL) { - if (sym.isPrimary() && - sym.getAddress().equals(function.getEntryPoint())) { - refreshName(); - } + if (sym.isPrimary() && sym.getAddress().equals(function.getEntryPoint())) { + scheduleRefreshName = true; + delayedUpdateMgr.updateLater(); } else if (inCurrentFunction(rec)) { - reloadFunction(); + scheduleReload = true; + delayedUpdateMgr.updateLater(); } break; case FUNCTION_CHANGED: @@ -217,15 +230,15 @@ public class StackEditorProvider case SYMBOL_REMOVED: case SYMBOL_ADDRESS_CHANGED: if (inCurrentFunction(rec)) { - reloadFunction(); + scheduleReload = true; + delayedUpdateMgr.updateLater(); } break; case SYMBOL_PRIMARY_STATE_CHANGED: sym = (Symbol) ((ProgramChangeRecord) rec).getNewValue(); - symType = sym.getSymbolType(); - if (symType == SymbolType.LABEL && - sym.getAddress().equals(function.getEntryPoint())) { - refreshName(); + if (sym.getAddress().equals(function.getEntryPoint())) { + scheduleRefreshName = true; + delayedUpdateMgr.updateLater(); } break; default: @@ -234,16 +247,6 @@ public class StackEditorProvider } } - private void reloadFunction() { - if (!stackModel.hasChanges()) { - stackModel.load(function); - } - else { - stackModel.stackChangedExternally(true); - editorPanel.setStatus("Stack may have been changed externally--data may be stale."); - } - } - private boolean inCurrentFunction(DomainObjectChangeRecord record) { if (!(record instanceof ProgramChangeRecord)) { return false; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackFrameDataType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackFrameDataType.java index 4608884402..7e7594b9d4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackFrameDataType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackFrameDataType.java @@ -15,93 +15,130 @@ */ package ghidra.app.plugin.core.stackeditor; -import java.util.Collections; -import java.util.Iterator; +import java.net.URL; +import java.util.*; +import javax.help.UnsupportedOperationException; + +import org.apache.commons.lang3.Range; +import org.apache.commons.lang3.StringUtils; + +import ghidra.app.plugin.core.compositeeditor.CompositeViewerDataTypeManager; import ghidra.docking.settings.Settings; +import ghidra.docking.settings.SettingsDefinition; +import ghidra.program.model.address.Address; import ghidra.program.model.data.*; +import ghidra.program.model.lang.ProgramArchitecture; import ghidra.program.model.listing.*; import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.pcode.Varnode; import ghidra.program.model.symbol.SymbolUtilities; -import ghidra.util.InvalidNameException; -import ghidra.util.SystemUtilities; -import ghidra.util.exception.AssertException; -import ghidra.util.exception.InvalidInputException; +import ghidra.util.*; +import ghidra.util.exception.*; /** - * StackFrameDataType provides an editable copy of a function stack frame. + * {@link StackFrameDataType} provides a {@link Structure} representation of a {@link StackFrame} + * for use by the Stack Frame Editor. Any other use is not supported since only those methods + * required by the editor have been implemented. This datatype is not intended to ever get + * resolved directly into a datatype manager. This implementation wraps a real {@link Structure} + * which may be resolved for the purpose of tracking datatype dependencies within the editor's + * dedicated datatype manager. + *

+ * NOTE: The {@link BadDataType} is utilized within the wrapped structure to preserve stack + * stack variables which have been defined with the {@link DataType#DEFAULT default datatype} + * since the wrapped structure would otherwise be unable to preserve a variable name or comment. */ -public class StackFrameDataType extends BiDirectionDataType { +class StackFrameDataType implements Structure { - static String DUMMY_FUNCTION_NAME = "StackWithoutFunction"; + /** + * Problematic areas: + * - Component replacement is trouble for compound storage + * - Auto-storage parameters should be protected since changes will be ignored (must use Function Editor). + * - Default ordinal-based parameter naming within editor does not account for other non-stack parameters + */ + + private static String STACK_STRUCTURE_NAME = "{{STACK_FRAME}}"; private static final String UNKNOWN_PREFIX = "unknown_"; - StackFrame stack; - int returnAddressOffset; - boolean growsNegative; - Function function; + private static final String STACK_PREFIX = "stack_"; + + // Partial VariableStorage serialization delimiters for use within comment string + private static final String SERIALIZATION_START = "_{{"; + private static final String SERIALIZATION_END = "}}_"; + + private final DataTypeManager dtm; + private Structure wrappedStruct; + + private int returnAddressOffset; + private boolean growsNegative; + private Function function; + + private int negativeLength; + private int positiveLength; + private int parameterOffset; // previously named BiDirectionalDataType.splitOffset /** * Constructor for an editable stack frame for use with the editor. * The specified stack will be copied into this new instance. * - * @param stack the function stack frame to be replicated. - * @param dtm datatype manager (required) + * @param function the function whose stack frame will be edited */ - public StackFrameDataType(StackFrame stack, DataTypeManager dtm) { - super( - (stack.getFunction() != null) ? stack.getFunction().getName() : "StackWithoutFunction", - 0, 0, stack.getParameterOffset(), dtm); - this.stack = stack; - initialize(); + StackFrameDataType(Function function) { + this.dtm = function.getProgram().getDataTypeManager(); + + // Initialize wrapped structure from stack frame variables + initializeFromStackFrame(function.getStackFrame()); } /** * Constructor for an editable stack frame for use with the editor. - * The specified stackDt will be copied into this new instance. + * The specified {@link StackFrameDataType} instance will be copied into this new instance * - * @param stackDt the function stack frame to be replicated. - * @param dtm datatype manager (required) + * @param stackDt stack frame editor datatype + * @param dtm dtm datatype manager (required) */ - public StackFrameDataType(StackFrameDataType stackDt, DataTypeManager dtm) { - super(stackDt.getName(), stackDt.getNegativeLength(), stackDt.getPositiveLength(), - stackDt.splitOffset, dtm); - setDescription(stackDt.getDescription()); - this.function = stackDt.function; - this.growsNegative = stackDt.growsNegative; + private StackFrameDataType(StackFrameDataType stackDt, DataTypeManager dtm) { + this.dtm = dtm; + + // NOTE: It is assumed that the program architecture will not change! + // Replicate state from specified stackDt instance + this.returnAddressOffset = stackDt.returnAddressOffset; - this.stack = stackDt.stack; - for (DataTypeComponentImpl dtc : stackDt.components) { - replaceAtOffset(dtc.getOffset(), dtc.getDataType(), dtc.getLength(), dtc.getFieldName(), - dtc.getComment()); + this.growsNegative = stackDt.growsNegative; + this.function = stackDt.function; + this.negativeLength = stackDt.negativeLength; + this.positiveLength = stackDt.positiveLength; + this.parameterOffset = stackDt.parameterOffset; + + this.wrappedStruct = new StructureDataType(STACK_STRUCTURE_NAME, stackDt.getLength(), dtm); + for (StackComponentWrapper wrappedDtc : stackDt.getDefinedComponents()) { + // Replicate stored structure component details + DataTypeComponent dtc = wrappedDtc.dtc; + wrappedStruct.replaceAtOffset(dtc.getOffset(), dtc.getDataType(), dtc.getLength(), + dtc.getFieldName(), dtc.getComment()); } } - StackFrame getStackFrame() { - return stack; - } + private void initializeFromStackFrame(StackFrame stack) { + + returnAddressOffset = stack.getReturnAddressOffset(); + growsNegative = stack.growsNegative(); + function = stack.getFunction(); + parameterOffset = stack.getParameterOffset(); - private void initialize() { - this.function = stack.getFunction(); int paramSize = stack.getParameterSize(); int localSize = stack.getLocalSize(); - this.returnAddressOffset = stack.getReturnAddressOffset(); - this.growsNegative = stack.growsNegative(); - int negLength; - int posLength; if (growsNegative) { // locals come first - negLength = -localSize; - posLength = paramSize; + negativeLength = localSize; + positiveLength = paramSize; } else { // params come first - negLength = -paramSize; - posLength = localSize; + negativeLength = paramSize; + positiveLength = localSize; } - growStructure(posLength); - growStructure(negLength); + wrappedStruct = new StructureDataType(STACK_STRUCTURE_NAME, stack.getFrameSize(), dtm); Variable[] stackVars = stack.getStackVariables(); for (int i = stackVars.length - 1; i >= 0; i--) { @@ -110,158 +147,128 @@ public class StackFrameDataType extends BiDirectionDataType { Varnode stackVarnode = storage.getLastVarnode(); int length = stackVarnode.getSize(); int offset = (int) stackVarnode.getOffset(); - if (offset < (negLength + splitOffset)) { + + // TODO: Investigate is joined-stack variables are reflected in Function stack related methods + // TODO: Why are the following checks needed? + if (offset < (parameterOffset - negativeLength)) { continue; } - if ((offset + length - 1) > (posLength + splitOffset)) { + if ((offset + length - 1) > (positiveLength + parameterOffset)) { continue; } - String comment = var.getComment(); - if (comment != null) { - comment = comment.trim(); - if (comment.length() == 0) { - comment = null; + + String comment = buildComment(var); + + DataType dt = var.getDataType(); + if (dt == DataType.DEFAULT) { + // Use BadDataType as placeholder within Structure + length = 1; + dt = BadDataType.dataType; + } + + doReplaceAtOffset(offset, dt, length, var.getName(), comment); + } + } + + private static String buildComment(Variable var) { + String nonStackStoragePart = getNonStackPartialSerializedStorage(var); + String comment = var.getComment(); + if (nonStackStoragePart != null) { + comment = StringUtils.join(nonStackStoragePart, comment); + } + return comment; + } + + private static String getNonStackPartialSerializedStorage(Variable var) { + VariableStorage storage = var.getVariableStorage(); + if (storage.getVarnodeCount() < 2) { + return null; + } + Varnode[] varnodes = storage.getVarnodes(); + Varnode[] partialVarnodes = new Varnode[varnodes.length - 1]; + System.arraycopy(varnodes, 0, partialVarnodes, 0, partialVarnodes.length); + String serializationString = VariableStorage.getSerializationString(partialVarnodes); + return SERIALIZATION_START + serializationString + SERIALIZATION_END; + } + + /** + * {@return newly generated stack variables based on the current state} + */ + Variable[] getStackVariables() { + StackComponentWrapper[] definedComponents = getDefinedComponents(); + Variable[] vars = new Variable[definedComponents.length]; + for (int i = 0; i < vars.length; i++) { + StackComponentWrapper dtc = definedComponents[i]; + String fieldName = dtc.getFieldName(); + VariableStorage storage = dtc.getVariableStorage(); + try { + vars[i] = new LocalVariableImpl(fieldName, 0, dtc.getDataType(), storage, + function.getProgram()); + } + catch (InvalidInputException e) { + try { + vars[i] = + new LocalVariableImpl(fieldName, 0, null, storage, function.getProgram()); + } + catch (InvalidInputException e1) { + throw new AssertException(); // Unexpected } } - String varName = var.getName(); - DataType dt = var.getDataType(); - if (!storage.isStackStorage()) { - // Compound storage where only the last piece is on the stack - // Create a datatype to represent this piece - dt = new StackPieceDataType(var, getDataTypeManager()); - } - replaceAtOffset(offset, dt, length, (isDefaultName(varName)) ? null : varName, comment); + vars[i].setComment(dtc.getComment()); } - } - - /* (non-Javadoc) - * @see ghidra.program.model.data.DataType#getRepresentation(ghidra.program.model.mem.MemBuffer, ghidra.util.settings.Settings, int) - */ - @Override - public String getRepresentation(MemBuffer buf, Settings settings, int length) { - return ""; - } - - boolean isDefaultName(String varName) { - if (varName == null) { - return false; - } - return SymbolUtilities.isDefaultLocalStackName(varName) || - SymbolUtilities.isDefaultParameterName(varName); - } - - @Override - public StackFrameDataType clone(DataTypeManager dtm) { - if (dtm == dataMgr) { - return this; - } - return new StackFrameDataType(this, dtm); - } - - @Override - public StackFrameDataType copy(DataTypeManager dtm) { - return new StackFrameDataType(this, dtm); - } - - int getMinOffset() { - return splitOffset - negativeLength; - } - - int getMaxOffset() { - return splitOffset + positiveLength - 1; - } - - public static String getHexString(int offset, boolean showPrefix) { - String prefix = showPrefix ? "0x" : ""; - return ((offset >= 0) ? (prefix + Integer.toHexString(offset)) - : ("-" + prefix + Integer.toHexString(-offset))); + return vars; } /** - * If a stack variable is defined in the editor at the specified offset, this retrieves the - * editor element containing that stack variable
- * Note: if a stack variable isn't defined at the indicated offset then null is returned. - * - * @param offset the offset - * @return the stack editor's element at the offset. Otherwise, null. + * Resolve the wrapped structure using the stack editor's datatype manager. This is + * done to facilitate datatype dependency tracking immediately following instantiation of + * this stack frame datatype which itself cannot be resolved. + *

+ * NOTE: It is required that this stack frame datatype instance be instantiated or copied + * using the original function's datyatype manager and that the editor's datatype manager + * has the same data organization. + * + * @param viewDTM stack editor's datatype manager */ - public DataTypeComponent getDefinedComponentAtOffset(int offset) { - if (offset < getMinOffset() || offset > getMaxOffset()) { - throw new ArrayIndexOutOfBoundsException(offset); - } - int index = Collections.binarySearch(components, Integer.valueOf(offset), offsetComparator); - if (index >= 0) { - return components.get(index); - } - return null; + void resolveWrappedComposite(CompositeViewerDataTypeManager viewDTM) { + wrappedStruct = (Structure) viewDTM.resolve(wrappedStruct, null); } /** - * If a stack variable is defined in the editor at the specified ordinal, this retrieves the - * editor element containing that stack variable.
- * Note: if a stack variable isn't defined for the indicated ordinal then null is returned. - * - * @param ordinal the ordinal - * @return the stack editor's element at the ordinal. Otherwise, null. + * {@return the original function which this stack frame corresponds to.} */ - public DataTypeComponent getDefinedComponentAtOrdinal(int ordinal) { - if (ordinal < 0 || ordinal >= getNumComponents()) { - throw new ArrayIndexOutOfBoundsException(ordinal); - } - int index = Collections.binarySearch(components, Integer.valueOf(ordinal), ordinalComparator); - if (index >= 0) { - return components.get(index); - } - return null; - } - - /* (non-Javadoc) - * @see ghidra.program.model.listing.StackFrame#getFunction() - */ - public Function getFunction() { + Function getFunction() { return function; } - /* (non-Javadoc) - * @see ghidra.program.model.listing.StackFrame#getFrameSize() - */ - public int getFrameSize() { - return getLength(); + public boolean growsNegative() { + return growsNegative; + } + + public int getParameterOffset() { + return parameterOffset; + } + + public int getNegativeLength() { + return negativeLength; + } + + public int getPositiveLength() { + return positiveLength; + } + + public int getFrameSize() { + return wrappedStruct.getLength(); } - /* (non-Javadoc) - * @see ghidra.program.model.listing.StackFrame#getLocalSize() - */ public int getLocalSize() { return growsNegative ? negativeLength : positiveLength; } - /* (non-Javadoc) - * @see ghidra.program.model.listing.StackFrame#getParameterSize() - */ public int getParameterSize() { - - // TODO: Does size of last positive param need to be factored in ?? - -// DataTypeComponent[] variables = getDefinedComponents(); -// int lastParamSize = 0; -// if (variables.length != 0) { -// DataTypeComponent var = variables[variables.length-1]; -// if (var.getOffset() >= getParameterOffset()) { -// lastParamSize = var.getLength(); -// } -// } - return growsNegative ? positiveLength : negativeLength; } - /* (non-Javadoc) - * @see ghidra.program.model.listing.StackFrame#getParameterOffset() - */ - public int getParameterOffset() { - return splitOffset; - } - public boolean setLocalSize(int size) { return adjustStackFrameSize(size, getLocalSize(), growsNegative); } @@ -270,6 +277,10 @@ public class StackFrameDataType extends BiDirectionDataType { return adjustStackFrameSize(newParamSize, getParameterSize(), !growsNegative); } + public int getReturnAddressOffset() { + return returnAddressOffset; + } + private boolean adjustStackFrameSize(int newSize, int oldSize, boolean isNegative) { if (newSize < 0) { return false; @@ -300,120 +311,106 @@ public class StackFrameDataType extends BiDirectionDataType { return true; } -// private int getFirstParameterLength() { -// Object offsetKey = Integer.valueOf(0); -// DataTypeComponent[] variables = getDefinedComponents(); -// int loc = Arrays.binarySearch(variables, offsetKey, offsetComparator); -// loc = (loc < 0 ? -1 - loc : loc); -// if (!growsNegative()) { -// loc--; -// } -// if (loc >= 0 && loc < variables.length) { -// DataTypeComponent var = variables[loc]; -// if (var != null) { -// return var.getLength(); -// } -// } -// return 0; -// } - - void shiftParamOffset(int offset, int deltaOrdinal, int deltaLength) { - int index = Collections.binarySearch(components, Integer.valueOf(offset), offsetComparator); - if (index < 0) { - index = -index - 1; - } - - adjustOffsets(index, offset, deltaOrdinal, deltaLength); - numComponents += deltaOrdinal; - notifySizeChanged(); - } - - /** - * Undefines any defined stack variables in the indicated offset range. - * - * @param minOffset the range's minimum offset on the stack frame - * @param maxOffset the range's maximum offset on the stack frame - */ - @SuppressWarnings("unused") - private void clearRange(int minOffset, int maxOffset) { - int first = Collections.binarySearch(components, Integer.valueOf(minOffset), offsetComparator); - if (first < 0) { - first = -first - 1; - } - int last = Collections.binarySearch(components, Integer.valueOf(maxOffset), offsetComparator); - if (last < 0) { - last = -last - 2; - } - for (int index = first; index < last; index++) { - clearComponent(index); - } - } - /** * Deletes the indicated range of bytes from this stack frame data type. * * @param minOffset the range's minimum offset on the stack frame * @param maxOffset the range's maximum offset on the stack frame */ - private void deleteRange(int minOffset, int maxOffset) { - // FUTURE: improve the efficiency of this. - int minOrdinal = getComponentAt(minOffset).getOrdinal(); - clearComponent(minOrdinal); - int maxOrdinal = getComponentAt(maxOffset).getOrdinal(); - clearComponent(maxOrdinal); - minOrdinal = getComponentAt(minOffset).getOrdinal(); - maxOrdinal = getComponentAt(maxOffset).getOrdinal(); - for (int i = maxOrdinal; i >= minOrdinal; i--) { - delete(i); - } - } + private void deleteRange(int minOffset, int maxOffset) throws IndexOutOfBoundsException { - /* (non-Javadoc) - * @see ghidra.program.model.listing.StackFrame#getReturnAddressOffset() - */ - public int getReturnAddressOffset() { - return returnAddressOffset; - } - - /* (non-Javadoc) - * @see ghidra.program.model.listing.StackFrame#clearVariable(int) - */ - public void clearComponentAt(int offset) { - if (offset < getMinOffset() || offset > getMaxOffset()) { - throw new ArrayIndexOutOfBoundsException(offset); + StackComponentWrapper dtc = getComponentContaining(minOffset); + if (dtc == null) { + return; // nothing to clear } - int index = Collections.binarySearch(components, Integer.valueOf(offset), offsetComparator); - if (index >= 0) { - clearComponent(index); - } - } - public Variable[] getStackVariables() { - Variable[] vars = new Variable[components.size()]; - Iterator iter = components.iterator(); - for (int i = 0; iter.hasNext(); i++) { - DataTypeComponent dtc = iter.next(); - String fieldName = dtc.getFieldName(); - int offset = dtc.getOffset(); - try { - vars[i] = new LocalVariableImpl(fieldName, dtc.getDataType(), offset, - function.getProgram()); - } - catch (InvalidInputException e) { - try { - vars[i] = new LocalVariableImpl(fieldName, null, offset, function.getProgram()); - } - catch (InvalidInputException e1) { - throw new AssertException(); // Unexpected + int space = maxOffset - minOffset + 1; + while (dtc != null && space > 0) { + int ordinal = dtc.getOrdinal(); + int len = dtc.getLength(); + if (len <= space) { + space -= len; + delete(ordinal); + int minOffsetLimit = parameterOffset - negativeLength; + if (minOffset < minOffsetLimit) { + minOffset = minOffsetLimit; } } - vars[i].setComment(dtc.getComment()); + else { + // Must clear component to breakdown into undefined bytes + clearComponent(ordinal); + } + dtc = getComponentContaining(minOffset); } - return vars; } - public boolean growsNegative() { - return growsNegative; + int getMinOffset() { + return parameterOffset - negativeLength; + } + + int getMaxOffset() { + return parameterOffset + positiveLength - 1; + } + + /** + * Effectively moves a component for a defined stack variable if it will fit where it is being + * moved to in the stack frame. + * + * @param ordinal the ordinal of the component to move by changing its offset. + * @param newOffset the offset to move the variable to. + * @return the component representing the stack variable at the new offset. + * @throws InvalidInputException if it can't be moved. + * @throws IndexOutOfBoundsException if the ordinal is out of bounds + */ + public StackComponentWrapper setOffset(int ordinal, int newOffset) + throws InvalidInputException, IndexOutOfBoundsException { + final StackComponentWrapper comp = getComponent(ordinal); + final int oldOffset = comp.getOffset(); + final int compLength = comp.getLength(); + if (newOffset == oldOffset) { + return comp; + } + + if ((oldOffset >= parameterOffset) && (newOffset < parameterOffset) || + (oldOffset < parameterOffset) && ((newOffset + compLength - 1) >= parameterOffset)) { + throw new InvalidInputException( + "Cannot move a stack variable/parameter across the parameter offset."); + } + + if ((oldOffset >= 0) && (newOffset < 0) || + (oldOffset < 0) && ((newOffset + compLength - 1) >= 0)) { + throw new InvalidInputException( + "Cannot move a stack variable/parameter across the 0-offset point."); + } + + StackComponentWrapper existing = getComponentContaining(newOffset); + if (existing == null) { + throw new InvalidInputException( + getHexString(newOffset, true) + " is not an offset in this stack frame."); + } + + if (!existing.isUndefined() && existing.getOffset() != comp.getOffset()) { + throw new InvalidInputException("There is already another stack variable at offset " + + getHexString(newOffset, true) + "."); + } + + final DataType dt = comp.getDataType(); + final String fieldName = comp.getFieldName(); + final String comment = comp.getComment(); + + clearComponent(ordinal); + + int mrl = getMaxLength(newOffset); + if ((mrl != -1) && (compLength > mrl)) { + doReplaceAtOffset(oldOffset, dt, compLength, fieldName, comment); // restore component + throw new InvalidInputException( + dt.getDisplayName() + " doesn't fit at offset " + getHexString(newOffset, true) + + ". It needs " + compLength + " bytes, but " + mrl + " bytes are available."); + } + + StackComponentWrapper newComp = + doReplaceAtOffset(newOffset, dt, compLength, fieldName, comment); + return newComp; } /** @@ -422,29 +419,41 @@ public class StackFrameDataType extends BiDirectionDataType { * @param ordinal the ordinal * @param name the new name. Null indicates the default name. * @return true if name change was successful, else false + * @throws IndexOutOfBoundsException if specified ordinal is out of range + * @throws IllegalArgumentException if name is invalid */ - public boolean setName(int ordinal, String name) { - validateName(ordinal, name); - DataTypeComponent comp = getComponent(ordinal); + public boolean setName(int ordinal, String name) throws IndexOutOfBoundsException { + + StackComponentWrapper comp = getComponent(ordinal); String fieldName = comp.getFieldName(); + if (name != null) { name = name.trim(); if (name.length() == 0 || isDefaultName(name)) { name = null; } } + if (SystemUtilities.isEqual(name, fieldName)) { return false; } - DataType dt = comp.getDataType(); - int length = comp.getLength(); - String comment = comp.getComment(); - if (canDefineComponent(dt, length, name, comment)) { - DataTypeComponent newComp = replace(comp.getOrdinal(), dt, length, name, comment); - return (newComp != null); + if (!canDefineComponent(comp.getDataType(), comp.getLength(), name, comp.getComment())) { + // NOTE: It is unclear if we should clear component here if it previously existed + clearComponent(ordinal); + } + else if (comp.isUndefined()) { + comp = replace(comp.getOrdinal(), DataType.DEFAULT, 1, name, null); + } + else { + try { + comp.dtc.setFieldName(name); + } + catch (DuplicateNameException e) { + // FIXME: Inconsistent API / how should names be validated and on which methods? + return false; + } } - clearComponent(ordinal); return true; } @@ -453,10 +462,14 @@ public class StackFrameDataType extends BiDirectionDataType { * * @param ordinal the ordinal * @param comment the new comment. + * @return true if comment change was successful, else false + * @throws IndexOutOfBoundsException if specified ordinal is out of range */ - public boolean setComment(int ordinal, String comment) { - DataTypeComponent comp = getComponent(ordinal); + public boolean setComment(int ordinal, String comment) throws IndexOutOfBoundsException { + + StackComponentWrapper comp = getComponent(ordinal); String oldComment = comp.getComment(); + if (comment != null) { comment = comment.trim(); if (comment.length() == 0) { @@ -471,20 +484,24 @@ public class StackFrameDataType extends BiDirectionDataType { else if (comment.equals(oldComment)) { return false; } - DataType dt = comp.getDataType(); - int length = comp.getLength(); - String fieldName = comp.getFieldName(); - if (canDefineComponent(dt, length, fieldName, comment)) { - DataTypeComponent newComp = replace(comp.getOrdinal(), dt, length, fieldName, comment); - return (newComp != null); + + if (!canDefineComponent(comp.getDataType(), comp.getLength(), comp.getFieldName(), + comment)) { + // NOTE: It is unclear if we should clear component here if it previously existed + clearComponent(ordinal); + } + else if (comp.isUndefined()) { + replace(comp.getOrdinal(), DataType.DEFAULT, 1, null, comment); + } + else { + // Set comment while preserving possible partial storage serialization + String partialSerializedStorage = comp.getPartialStorageSerialization(true); + comment = StringUtils.join(partialSerializedStorage, comment); + comp.dtc.setComment(comment); } - clearComponent(ordinal); return true; } - /** - * @return - */ private boolean canDefineComponent(DataType dt, int length, String newName, String comment) { if (comment != null) { comment = comment.trim(); @@ -500,188 +517,129 @@ public class StackFrameDataType extends BiDirectionDataType { } /** - * Currently no validation is done on the name. - * - * @param ordinal - * @param newName - * @throws InvalidNameException - */ - void validateName(int ordinal, String newName) -// throws InvalidNameException - { -// if (newName == null || newName.length() == 0) { -// return; -// } -// // FUTURE: check that name is unique. -// ListIterator iter = components.listIterator(); -// while (iter.hasNext()) { -// DataTypeComponent element = (DataTypeComponent) iter.next(); -// if (element.getOrdinal() == ordinal) { -// continue; -// } -// if (newName.equals(element.getFieldName())) { -// throw new InvalidNameException("The name \""+newName -// +"\" is already in use at offset " -// +getHexString(element.getOffset(), true)+"."); -// } -// } - } - - /** - * Effectively moves a component for a defined stack variable if it will fit where it is being - * moved to in the stack frame. - * - * @param ordinal the ordinal of the component to move by changing its offset. - * @param newOffset the offset to move the variable to. - * @return the component representing the stack variable at the new offset. - * @throws InvalidInputException if it can't be moved. - */ - public DataTypeComponent setOffset(int ordinal, int newOffset) throws InvalidInputException { - DataTypeComponent comp = getComponent(ordinal); - int oldOffset = comp.getOffset(); - int compLength = comp.getLength(); - if (newOffset == oldOffset) { - return comp; - } - if ((oldOffset >= splitOffset) && (newOffset < splitOffset) || - (oldOffset < splitOffset) && (newOffset >= splitOffset)) { - throw new InvalidInputException( - "Cannot move a stack variable/parameter across the parameter offset."); - } - clearComponent(ordinal); - DataTypeComponent existing = getDefinedComponentAt(newOffset); - if (existing != null) { - replaceAtOffset(oldOffset, comp.getDataType(), comp.getLength(), comp.getFieldName(), - comp.getComment()); - throw new InvalidInputException("There is already a stack variable at offset " + - getHexString(newOffset, true) + "."); - } - existing = getComponentAt(newOffset); - int mrl = getMaxLength(newOffset); - if ((mrl != -1) && (compLength > mrl)) { - replaceAtOffset(oldOffset, comp.getDataType(), comp.getLength(), comp.getFieldName(), - comp.getComment()); - throw new InvalidInputException(comp.getDataType().getDisplayName() + - " doesn't fit at offset " + getHexString(newOffset, true) + ". It needs " + - compLength + " bytes, but " + mrl + " bytes are available."); - } - String defaultName = getDefaultName(comp); - String oldName = comp.getFieldName(); - boolean isDefault = (oldName == null) || (oldName.equals(defaultName)); - DataTypeComponent newComp = replaceAtOffset(newOffset, comp.getDataType(), comp.getLength(), - isDefault ? null : oldName, comp.getComment()); - return newComp; - } - - /** - * Sets a component representing the defined stack variable at the indicated ordinal to have the - * specified data type and length. + * Sets a stack component/variable data type * * @param ordinal the ordinal - * @param type the data type + * @param dataType the data type * @param length the length or size of this variable. * @return the component representing this stack variable. + * @throws IndexOutOfBoundsException if specified ordinal is out of range */ - public DataTypeComponent setDataType(int ordinal, DataType type, int length) { - DataTypeComponent dtc = getComponent(ordinal); - return replace(ordinal, type, length, dtc.getFieldName(), dtc.getComment()); + public StackComponentWrapper setDataType(int ordinal, DataType dataType, int length) + throws IndexOutOfBoundsException { + StackComponentWrapper stackDtc = getComponent(ordinal); + return replace(ordinal, dataType, length, stackDtc.getFieldName(), stackDtc.getComment()); } /** * Get the maximum variable size that will fit at the indicated offset if a replace is done. * - * @param offset + * @param stackOffset stack offset * @return the maximum size */ - public int getMaxLength(int offset) { - if (offset < getMinOffset() || offset > getMaxOffset()) { - throw new ArrayIndexOutOfBoundsException(offset); + public int getMaxLength(int stackOffset) { + + int structOffset = computeStructOffsetFromStackOffset(stackOffset, true); + + DataTypeComponent dtc = wrappedStruct.getComponentContaining(structOffset); + int nextDefinedOffset = wrappedStruct.getLength(); + if (dtc != null) { + dtc = wrappedStruct.getDefinedComponentAtOrAfterOffset(dtc.getEndOffset() + 1); + if (dtc != null) { + nextDefinedOffset = dtc.getOffset(); + } } - int nextOffset = offset; - int index = Collections.binarySearch(components, Integer.valueOf(offset), offsetComparator); - if (index >= 0) { - index++; - } - else { - index = -index - 1; - } - if (index < components.size()) { - nextOffset = (components.get(index)).getOffset(); - } - else { - nextOffset = getMaxOffset() + 1; - } - if ((offset < 0) && (nextOffset > 0)) { + int nextStackOffset = computeStackOffsetFromStructOffset(nextDefinedOffset); + + if (stackOffset < 0 && nextStackOffset > 0) { // Don't allow the new data type to cross from negative into positive stack space. - nextOffset = 0; + nextStackOffset = 0; } - if ((offset < splitOffset) && (nextOffset > splitOffset)) { + if ((stackOffset < parameterOffset) && (nextStackOffset >= parameterOffset)) { // Don't allow the new data type to cross from local into parameter stack space. - nextOffset = splitOffset; + nextStackOffset = parameterOffset; } - return nextOffset - offset; + return nextStackOffset - stackOffset; + } + + /** + * Determine if the specified variable name matches the default naming pattern. + * @param varName variable name. + * @return true if name matches default naming pattern, else false + */ + boolean isDefaultName(String varName) { + if (varName == null) { + return false; + } + if (varName.startsWith(STACK_PREFIX)) { + // Detect use of stack_ prefix for default names + varName = varName.substring(STACK_PREFIX.length()); + } + return SymbolUtilities.isDefaultLocalStackName(varName) || + SymbolUtilities.isDefaultParameterName(varName); } /** * Returns the default name for the indicated stack offset. * - * @param offset + * @param stackComponent stack element * @return the default stack variable name. */ - public String getDefaultName(DataTypeComponent element) { - int offset = element.getOffset(); + public String getDefaultName(StackComponentWrapper stackComponent) { + int offset = stackComponent.getOffset(); int paramBaseOffset = getParameterOffset(); boolean isLocal = growsNegative ? (offset < paramBaseOffset) : (offset >= paramBaseOffset); if (isLocal) { return SymbolUtilities.getDefaultLocalName(function.getProgram(), offset, 0); } - int index = getParameterIndex(element); + // NOTE: We really cannot produce good ordinal-based default param names since this does not + // account for non-stack parameteres + int index = getParameterIndex(stackComponent); if (index >= 0) { - return SymbolUtilities.getDefaultParamName(index); + return STACK_PREFIX + SymbolUtilities.getDefaultParamName(index); } return UNKNOWN_PREFIX + Integer.toHexString(Math.abs(offset)); } /** - * @param element + * @param stackElement stack element * @return the index number for this parameter (starting at 1 for the first parameter.) 0 if the * element is not a parameter. */ - private int getParameterIndex(DataTypeComponent element) { - int numComps = components.size(); - int firstIndex = -1; // first parameter - int myIndex = -1; // my parameter + private int getParameterIndex(StackComponentWrapper stackElement) { + int structOffset = stackElement.getOffset(); + StackComponentWrapper[] definedComponents = getDefinedComponents(); + int numComps = definedComponents.length; + int firstIndex = -1; // first parameter index if (growsNegative) { - for (int i = numComps - 1; (i >= 0); i--) { - DataTypeComponent dtc = components.get(i); + for (int i = 0; i < numComps; i++) { + DataTypeComponent dtc = definedComponents[i]; int currentOffset = dtc.getOffset(); - if (currentOffset < splitOffset) { - break; + if (currentOffset < parameterOffset) { + continue; } - firstIndex = i; - if (dtc == element) { - myIndex = i; + if (firstIndex < 0) { + firstIndex = i; + } + if (currentOffset == structOffset) { + return i - firstIndex; } - } - if (myIndex >= 0) { - return (myIndex - firstIndex); } } else { - for (int i = 0; (i < numComps); i++) { - DataTypeComponent dtc = components.get(i); + for (int i = numComps - 1; i >= 0; i--) { + DataTypeComponent dtc = definedComponents[i]; int currentOffset = dtc.getOffset(); - if (currentOffset >= splitOffset) { - break; + if (currentOffset >= parameterOffset) { + continue; } - firstIndex = i; - if (dtc == element) { - myIndex = i; + if (firstIndex < 0) { + firstIndex = i; + } + if (currentOffset == structOffset) { + return firstIndex - i; } - } - if (myIndex >= 0) { - return (firstIndex - myIndex); } } return 0; @@ -690,40 +648,921 @@ public class StackFrameDataType extends BiDirectionDataType { /** * Returns true if a stack variable is defined at the specified ordinal. * - * @param ordinal + * @param ordinal stack frame ordinal * @return true if variable is defined at ordinal or false if undefined. */ public boolean isStackVariable(int ordinal) { - if (ordinal < 0 || ordinal >= getNumComponents()) { + DataTypeComponent stackElement = getDefinedComponentAtOrdinal(ordinal); + return stackElement != null; + } + + /** + * {@return true if the specified stackOffset corresponds to a function parameter} + * @param stackOffset stack frame offset + */ + boolean isParameterOffset(int stackOffset) { + int paramStart = getParameterOffset(); + return (growsNegative && stackOffset >= paramStart) || + (!growsNegative && stackOffset < paramStart); + } + + /** + * Determine if the specified stackOffset corresponds to a parameter which should not be + * modified via the stack editor other than its name and comment. This is neccessary when + * (i.e., custom storage is disabled) in which case the function signature should be adjusted + * using the Function Editor. + * + * @param stackOffset stack frame offset + * @return true if the specified stackOffset corresponds to a protected parameter with + * an auto storage assignment. + */ + boolean isProtectedParameterOffset(int stackOffset) { + return isParameterOffset(stackOffset) && !function.hasCustomVariableStorage(); + } + + /** + * Get a formatted signed-hex value + * @param offset the value to be formatted + * @param showPrefix if true the "0x" hex prefix will be included + * @return formatted signed-hex value + */ + public static String getHexString(int offset, boolean showPrefix) { + String prefix = showPrefix ? "0x" : ""; + return ((offset >= 0) ? (prefix + Integer.toHexString(offset)) + : ("-" + prefix + Integer.toHexString(-offset))); + } + + private int computeStructOffsetFromStackOffset(int stackOffset, boolean doCheckDefinedOffset) { + int structOffset = stackOffset + negativeLength - parameterOffset; // parameterOffset - negativeLength + stackOffset; + if (doCheckDefinedOffset && + (structOffset < 0 || structOffset >= wrappedStruct.getLength())) { + throw new IllegalArgumentException("Offset " + getHexString(stackOffset, true) + + " is not a defined within the stack frame"); + } + return structOffset; + } + + private int computeStackOffsetFromStructOffset(int structOffset) { + return structOffset - negativeLength + parameterOffset; // structOffset + negativeLength - parameterOffset; + } + + /** + * If a stack variable is defined in the editor at the specified offset, this retrieves the + * editor element containing that stack variable
+ * Note: if a stack variable isn't defined at the indicated offset then null is returned. + * + * @param stackOffset the stack offset + * @return the stack editor's element at the stackOffset. Otherwise, null. + */ + public StackComponentWrapper getDefinedComponentAtOffset(int stackOffset) { + StackComponentWrapper stackDtc = getDefinedComponentAtOrAfterOffset(stackOffset); + if (stackDtc != null && stackDtc.getOffset() == stackOffset) { + return stackDtc; + } + return null; + } + + /** + * If a stack variable is defined in the editor at the specified ordinal, this retrieves the + * editor element containing that stack variable.
+ * + * @param ordinal the ordinal + * @return the stack editor's element at the ordinal or null if an undefined location within + * the bounds of the stack. + * @throws IndexOutOfBoundsException if the ordinal is out of bounds + */ + public StackComponentWrapper getDefinedComponentAtOrdinal(int ordinal) + throws IndexOutOfBoundsException { + DataTypeComponent dtc = wrappedStruct.getComponent(ordinal); + return (dtc != null && !dtc.isUndefined()) ? new StackComponentWrapper(dtc) : null; + } + + private void validateStackComponentDataType(DataType dataType) { + if (DataTypeComponent.usesZeroLengthComponent(dataType)) { + throw new IllegalArgumentException( + "Zero-length datatype not permitted: " + dataType.getName()); + } + if (dataType instanceof BitFieldDataType) { + throw new IllegalArgumentException("Bitfield not permitted: " + dataType.getName()); + } + } + + /** + * {@link StackComponentWrapper} wraps and standard {@link Structure} + * {@link DataTypeComponent} and provides the neccessary stack offset + * translation. + */ + class StackComponentWrapper implements DataTypeComponent { + + final DataTypeComponent dtc; + + StackComponentWrapper(DataTypeComponent dtc) { + this.dtc = dtc; + if (dtc instanceof StackComponentWrapper) { + // Must not wrap the wrapper + throw new IllegalArgumentException(); + } + } + + @Override + public DataType getDataType() { + DataType dt = dtc.getDataType(); + if (dt instanceof BadDataType && getLength() == 1) { + return DataType.DEFAULT; + } + return dt; + } + + @Override + public DataType getParent() { + return StackFrameDataType.this; + } + + @Override + public boolean isBitFieldComponent() { return false; } - int index = Collections.binarySearch(components, Integer.valueOf(ordinal), ordinalComparator); - if (index >= 0) { - return true; + + @Override + public boolean isZeroBitFieldComponent() { + return false; } + + @Override + public int getOrdinal() { + return dtc.getOrdinal(); + } + + /** + * {@return true if this component corresponds to a function parameter} + */ + boolean isParameter() { + int paramStart = getParameterOffset(); + int stackOffset = getOffset(); + return (growsNegative && stackOffset >= paramStart) || + (!growsNegative && stackOffset < paramStart); + } + + /** + * Determine if this component corresponds to a parameter which should not be + * modified via the stack editor other than its name and comment. This is neccessary when + * (i.e., custom storage is disabled) in which case the function signature should be adjusted + * using the Function Editor. + * + * @return true if this component corresponds to a protected parameter with + * an auto storage assignment. + */ + boolean isProtectedParameter() { + return isParameter() && !function.hasCustomVariableStorage(); + } + + @Override + public int getOffset() { + return computeStackOffsetFromStructOffset(dtc.getOffset()); + } + + @Override + public int getEndOffset() { + return computeStackOffsetFromStructOffset(dtc.getEndOffset()); + } + + @Override + public int getLength() { + return dtc.getLength(); + } + + /** + * Unsupported method. Must use {@link StackFrameDataType#setComment(int, String)}. + */ + @Override + public void setComment(String comment) { + throw new UnsupportedOperationException(); + } + + @Override + public String getComment() { + String comment = dtc.getComment(); + if (comment != null && comment.startsWith(SERIALIZATION_START)) { + int ix = comment.indexOf(SERIALIZATION_END); + if (ix > 0) { + comment = comment.substring(ix + SERIALIZATION_END.length()); + } + } + return StringUtils.isBlank(comment) ? null : comment; + } + + private String getPartialStorageSerialization(boolean stripStartEnd) { + String comment = dtc.getComment(); + if (comment != null && comment.startsWith(SERIALIZATION_START)) { + int ix = comment.indexOf(SERIALIZATION_END); + if (ix > 0) { + int startIx = stripStartEnd ? SERIALIZATION_START.length() : 0; + int endIx = ix + (stripStartEnd ? 0 : SERIALIZATION_END.length()); + return comment.substring(startIx, endIx); + } + } + return null; + } + + private VariableStorage getVariableStorage() { + ProgramArchitecture programArchitecture = dtm.getProgramArchitecture(); + + // Extract partial non-stack storage serialization from comment + Varnode[] partialStorage = null; + String partialSerializedStorage = getPartialStorageSerialization(true); + if (partialSerializedStorage != null) { + try { + partialStorage = + VariableStorage.deserialize(programArchitecture, partialSerializedStorage) + .getVarnodes(); + } + catch (InvalidInputException e) { + // ignore + } + } + + try { + Address stackAddr = + programArchitecture.getAddressFactory().getStackSpace().getAddress(getOffset()); + Varnode stackVarnode = new Varnode(stackAddr, getLength()); + + if (partialStorage != null) { + Varnode[] joinedVarnodes = new Varnode[partialStorage.length + 1]; + System.arraycopy(partialStorage, 0, joinedVarnodes, 0, partialStorage.length); + joinedVarnodes[partialStorage.length] = stackVarnode; + return new VariableStorage(programArchitecture, joinedVarnodes); + } + + return new VariableStorage(programArchitecture, stackVarnode); + } + catch (InvalidInputException e) { + Msg.error(this, "Failed to build variable: " + e.getMessage()); + try { + // fallback on error to single byte stack varnode + return new VariableStorage(programArchitecture, getOffset(), 1); + } + catch (InvalidInputException e1) { + throw new AssertException(e1); // unexpected + } + } + } + + @Override + public Settings getDefaultSettings() { + return dtc.getDefaultSettings(); + } + + @Override + public String getFieldName() { + return dtc.getFieldName(); + } + + /** + * Unsupported method. Must use {@link StackFrameDataType#setName(int, String)}. + */ + @Override + public void setFieldName(String fieldName) throws DuplicateNameException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEquivalent(DataTypeComponent otherDtc) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isUndefined() { + return dtc.isUndefined(); + } + + @Override + public String toString() { + return InternalDataTypeComponent.toString(this); + } + } + + // + // Supported Structure methods. Some methods may need to handle stack offset transformation + // + + @Override + public StackFrameDataType clone(DataTypeManager dataMgr) { + if (dtm == dataMgr) { + return this; + } + return copy(dataMgr); + } + + @Override + public StackFrameDataType copy(DataTypeManager dataMgr) { + return new StackFrameDataType(this, dataMgr); + } + + @Override + public DataTypeManager getDataTypeManager() { + return dtm; + } + + @Override + public DataOrganization getDataOrganization() { + return wrappedStruct.getDataOrganization(); + } + + @Override + public int getLength() { + return wrappedStruct.getLength(); + } + + @Override + public String getName() { + return function.getName(); + } + + @Override + public String getPathName() { + return getName(); + } + + @Override + public String getDisplayName() { + return getName(); + } + + @Override + public boolean isEquivalent(DataType dt) { + if (!(dt instanceof StackFrameDataType stackDt) || function != stackDt.function) { + throw new IllegalStateException("Expected the same function for supported use"); + } + return wrappedStruct.isEquivalent(stackDt.wrappedStruct); + } + + @Override + public String getDescription() { + return "Stack Frame: " + getName(); + } + + @Override + public int getAlignedLength() { + return getLength(); + } + + @Override + public boolean isZeroLength() { return false; } -// public static void main(String[] args) { -// StackFrameImpl sf = new StackFrameImpl(0x4); -// sf.setLocalSize(0xd); -// sf.setParameterOffset(0x4); -// sf.setReturnAddressOffset(0x0); -// sf.createVariable("local_c", new Undefined1(), -0xc, 1); -// sf.createVariable("local_8", new Undefined4(), -0x8, 4); -// sf.createVariable("param_4", new DWordDataType(), 0x4, 4); -// sf.createVariable("param_8", new FloatDataType(), 0x8, 4); -// StackFrameDataType es = new StackFrameDataType(sf); -// int numElements = es.getNumComponents(); -//// System.out.println("\nElements by ORDINAL"); -// for (int i = 0; i < numElements; i++) { -// es.getComponent(i); -// } -//// System.out.println("\nElements by OFFSET"); -// for (int i = es.getMinOffset(); i < es.getMaxOffset(); i++) { -// es.getComponentAt(i); -// } -// System.exit(0); -// } + @Override + public boolean isNotYetDefined() { + return false; + } + + @Override + public PackingType getPackingType() { + return PackingType.DISABLED; + } + + @Override + public int getNumComponents() { + return wrappedStruct.getNumComponents(); + } + + @Override + public int getNumDefinedComponents() { + return wrappedStruct.getNumDefinedComponents(); + } + + @Override + public CategoryPath getCategoryPath() { + return CategoryPath.ROOT; + } + + @Override + public DataTypePath getDataTypePath() { + return new DataTypePath(CategoryPath.ROOT, getName()); + } + + @Override + public StackComponentWrapper[] getDefinedComponents() { + DataTypeComponent[] components = wrappedStruct.getDefinedComponents(); + StackComponentWrapper[] wrappedComponents = new StackComponentWrapper[components.length]; + for (int i = 0; i < components.length; i++) { + wrappedComponents[i] = new StackComponentWrapper(components[i]); + } + return wrappedComponents; + } + + @Override + public StackComponentWrapper getDefinedComponentAtOrAfterOffset(int stackOffset) { + int structOffset = computeStructOffsetFromStackOffset(stackOffset, false); + DataTypeComponent dtc = wrappedStruct.getDefinedComponentAtOrAfterOffset(structOffset); + return dtc != null ? new StackComponentWrapper(dtc) : null; + } + + @Override + public StackComponentWrapper getComponentContaining(int stackOffset) { + int structOffset = computeStructOffsetFromStackOffset(stackOffset, false); + DataTypeComponent dtc = wrappedStruct.getComponentContaining(structOffset); + return dtc != null ? new StackComponentWrapper(dtc) : null; + } + + @Override + public StackComponentWrapper getComponent(int ordinal) throws IndexOutOfBoundsException { + DataTypeComponent dtc = wrappedStruct.getComponent(ordinal); + return new StackComponentWrapper(dtc); + } + + @Override + public void clearComponent(int ordinal) throws IndexOutOfBoundsException { + wrappedStruct.clearComponent(ordinal); + } + + @Override + public void clearAtOffset(int stackOffset) { + int structOffset = computeStructOffsetFromStackOffset(stackOffset, true); + wrappedStruct.clearAtOffset(structOffset); + } + + @Override + public void delete(int ordinal) throws IndexOutOfBoundsException { + StackComponentWrapper dtc = getComponent(ordinal); + if (dtc == null) { + return; + } + int stackOffset = dtc.getOffset(); + int len = dtc.getLength(); + + wrappedStruct.delete(ordinal); + + Range r = Range.between(stackOffset, stackOffset + len - 1); + if (r.contains(parameterOffset)) { + int negLenReduction = parameterOffset - stackOffset; + negativeLength -= negLenReduction; + positiveLength -= len - negLenReduction; + } + else if (r.isBefore(parameterOffset)) { + negativeLength -= len; + } + else { + positiveLength -= len; + } + } + + @Override + public StackComponentWrapper[] getComponents() { + DataTypeComponent[] components = wrappedStruct.getComponents(); + StackComponentWrapper[] wrappedComponents = new StackComponentWrapper[components.length]; + for (int i = 0; i < components.length; i++) { + wrappedComponents[i] = new StackComponentWrapper(components[i]); + } + return wrappedComponents; + } + + @Override + public StackComponentWrapper getComponentAt(int stackOffset) { + int structOffset = computeStructOffsetFromStackOffset(stackOffset, false); + DataTypeComponent dtc = wrappedStruct.getComponentAt(structOffset); + return dtc != null ? new StackComponentWrapper(dtc) : null; + } + + @Override + public void growStructure(int amount) { + + if (amount < 0) { + negativeLength -= amount; + + // Push all defined components down based on negative size increase. + // Since we cannot directly manipulate offsets we must insert a defined component + // then clear it. + wrappedStruct.insert(0, Undefined.getUndefinedDataType(-amount)); + wrappedStruct.clearComponent(0); + } + else { + positiveLength += amount; + wrappedStruct.growStructure(amount); + } + } + + /** + * Check for possible stack growth and adjust positiveLength as needed. + * This method shuold be invoked if changes are made to the wrapped structure by the + * datatype manager in response to datatype dependency changes they may trigger positive + * growth. + */ + void checkForStackGrowth() { + int delta = wrappedStruct.getLength() - positiveLength - negativeLength; + if (delta > 0) { + positiveLength += delta; + } + } + + private StackComponentWrapper doReplaceAtOffset(int stackOffset, DataType dataType, int length, + String name, String comment) throws IllegalArgumentException { + + int structOffset = computeStructOffsetFromStackOffset(stackOffset, true); + + validateStackComponentDataType(dataType); + + if (dataType == DataType.DEFAULT) { + dataType = BadDataType.dataType; + length = 1; + } + + if (name != null && isDefaultName(name)) { + name = null; + } + + DataTypeComponent dtc = + wrappedStruct.replaceAtOffset(structOffset, dataType, length, name, comment); + + checkForStackGrowth(); + + return new StackComponentWrapper(dtc); + } + + @Override + public StackComponentWrapper replace(int ordinal, DataType dataType, int length, String name, + String comment) throws IndexOutOfBoundsException, IllegalArgumentException { + + validateStackComponentDataType(dataType); + + if (dataType == DataType.DEFAULT) { + dataType = BadDataType.dataType; + length = 1; + } + + if (name != null && isDefaultName(name)) { + name = null; + } + + DataTypeComponent dtc = wrappedStruct.replace(ordinal, dataType, length, name, comment); + + checkForStackGrowth(); + + return new StackComponentWrapper(dtc); + } + + // + // Unused/Unsupported Structure methods + // Implementation is tailored specifically for use for Stack Editor + // + + @Override + public void setName(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public void setLength(int length) { + throw new UnsupportedOperationException(); + } + + @Override + public void setDescription(String desc) { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeComponent add(DataType dataType) { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeComponent add(DataType dataType, int length) { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeComponent add(DataType dataType, String name, String comment) { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeComponent add(DataType dataType, int length, String name, String comment) { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeComponent addBitField(DataType baseDataType, int bitSize, String componentName, + String comment) { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeComponent insert(int ordinal, DataType dataType) { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeComponent insert(int ordinal, DataType dataType, int length) { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeComponent insert(int ordinal, DataType dataType, int length, String name, + String comment) { + throw new UnsupportedOperationException(); + } + + @Override + public void deleteAtOffset(int stackOffset) { + throw new UnsupportedOperationException(); + } + + @Override + public void delete(Set ordinals) throws IndexOutOfBoundsException { + throw new UnsupportedOperationException(); + } + + @Override + public void deleteAll() { + throw new UnsupportedOperationException(); + } + + @Override + public StackComponentWrapper replaceAtOffset(int stackOffset, DataType dataType, int length, + String newName, String comment) throws IllegalArgumentException { + throw new UnsupportedOperationException(); + } + + @Override + public StackComponentWrapper replace(int ordinal, DataType dataType, int length) + throws IndexOutOfBoundsException, IllegalArgumentException { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeComponent insertAtOffset(int offset, DataType dataType, int length) { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeComponentImpl insertAtOffset(int offset, DataType dataType, int length, + String newName, String comment) { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeComponent insertBitField(int ordinal, int byteWidth, int bitOffset, + DataType baseDataType, int bitSize, String componentName, String comment) { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeComponent insertBitFieldAt(int byteOffset, int byteWidth, int bitOffset, + DataType baseDataType, int bitSize, String componentName, String comment) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isPartOf(DataType dataType) { + throw new UnsupportedOperationException(); + } + + @Override + public void dataTypeAlignmentChanged(DataType dt) { + throw new UnsupportedOperationException(); + } + + @Override + public void repack() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPackingEnabled(boolean enabled) { + throw new UnsupportedOperationException(); + } + + @Override + public int getExplicitPackingValue() { + throw new UnsupportedOperationException(); + } + + @Override + public void setExplicitPackingValue(int packingValue) { + throw new UnsupportedOperationException(); + } + + @Override + public void setToDefaultPacking() { + throw new UnsupportedOperationException(); + } + + @Override + public int getAlignment() { + throw new UnsupportedOperationException(); + } + + @Override + public AlignmentType getAlignmentType() { + throw new UnsupportedOperationException(); + } + + @Override + public int getExplicitMinimumAlignment() { + throw new UnsupportedOperationException(); + } + + @Override + public void setExplicitMinimumAlignment(int minAlignment) { + throw new UnsupportedOperationException(); + } + + @Override + public void setToDefaultAligned() { + throw new UnsupportedOperationException(); + } + + @Override + public void setToMachineAligned() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasLanguageDependantLength() { + throw new UnsupportedOperationException(); + } + + @Override + public SettingsDefinition[] getSettingsDefinitions() { + throw new UnsupportedOperationException(); + } + + @Override + public TypeDefSettingsDefinition[] getTypeDefSettingsDefinitions() { + throw new UnsupportedOperationException(); + } + + @Override + public Settings getDefaultSettings() { + throw new UnsupportedOperationException(); + } + + @Override + public void setCategoryPath(CategoryPath path) { + throw new UnsupportedOperationException(); + } + + @Override + public void setNameAndCategory(CategoryPath path, String name) { + throw new UnsupportedOperationException(); + } + + @Override + public String getMnemonic(Settings settings) { + throw new UnsupportedOperationException(); + } + + @Override + public URL getDocs() { + throw new UnsupportedOperationException(); + } + + @Override + public Object getValue(MemBuffer buf, Settings settings, int length) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEncodable() { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] encodeValue(Object value, MemBuffer buf, Settings settings, int length) { + throw new UnsupportedOperationException(); + } + + @Override + public Class getValueClass(Settings settings) { + throw new UnsupportedOperationException(); + } + + @Override + public String getDefaultLabelPrefix() { + throw new UnsupportedOperationException(); + } + + @Override + public String getDefaultAbbreviatedLabelPrefix() { + throw new UnsupportedOperationException(); + } + + @Override + public String getDefaultLabelPrefix(MemBuffer buf, Settings settings, int len, + DataTypeDisplayOptions options) { + throw new UnsupportedOperationException(); + } + + @Override + public String getDefaultOffcutLabelPrefix(MemBuffer buf, Settings settings, int len, + DataTypeDisplayOptions options, int offcutOffset) { + throw new UnsupportedOperationException(); + } + + @Override + public String getRepresentation(MemBuffer buf, Settings settings, int length) { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] encodeRepresentation(String repr, MemBuffer buf, Settings settings, int length) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDeleted() { + throw new UnsupportedOperationException(); + } + + @Override + public void dataTypeSizeChanged(DataType dt) { + throw new UnsupportedOperationException(); + } + + @Override + public void dataTypeDeleted(DataType dt) { + throw new UnsupportedOperationException(); + } + + @Override + public void dataTypeReplaced(DataType oldDt, DataType newDt) { + throw new UnsupportedOperationException(); + } + + @Override + public void dataTypeNameChanged(DataType dt, String oldName) { + throw new UnsupportedOperationException(); + } + + @Override + public void addParent(DataType dt) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeParent(DataType dt) { + throw new UnsupportedOperationException(); + } + + @Override + public Collection getParents() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean dependsOn(DataType dt) { + throw new UnsupportedOperationException(); + } + + @Override + public SourceArchive getSourceArchive() { + throw new UnsupportedOperationException(); + } + + @Override + public void setSourceArchive(SourceArchive archive) { + throw new UnsupportedOperationException(); + } + + @Override + public long getLastChangeTime() { + throw new UnsupportedOperationException(); + } + + @Override + public long getLastChangeTimeInSourceArchive() { + throw new UnsupportedOperationException(); + } + + @Override + public UniversalID getUniversalID() { + throw new UnsupportedOperationException(); + } + + @Override + public void replaceWith(DataType dataType) { + throw new UnsupportedOperationException(); + } + + @Override + public void setLastChangeTime(long lastChangeTime) { + throw new UnsupportedOperationException(); + } + + @Override + public void setLastChangeTimeInSourceArchive(long lastChangeTimeInSourceArchive) { + throw new UnsupportedOperationException(); + } + + @Override + public List getComponentsContaining(int offset) { + throw new UnsupportedOperationException(); + } + + @Override + public DataTypeComponent getDataTypeAt(int offset) { + throw new UnsupportedOperationException(); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackPieceDataType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackPieceDataType.java deleted file mode 100644 index 304e6a8d1c..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackPieceDataType.java +++ /dev/null @@ -1,131 +0,0 @@ -/* ### - * 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.stackeditor; - -import ghidra.docking.settings.Settings; -import ghidra.program.model.data.*; -import ghidra.program.model.listing.Variable; -import ghidra.program.model.listing.VariableStorage; -import ghidra.program.model.mem.MemBuffer; -import ghidra.program.model.pcode.Varnode; -import ghidra.util.InvalidNameException; -import ghidra.util.exception.DuplicateNameException; - -public class StackPieceDataType extends DataTypeImpl { - - private final Variable variable; - - StackPieceDataType(Variable var, DataTypeManager dataMgr) { - super(CategoryPath.ROOT, getPieceName(var), dataMgr); - variable = var; - } - - private static String getPieceName(Variable var) { - VariableStorage storage = var.getVariableStorage(); - Varnode stackVarnode = storage.getLastVarnode(); - int pieceLen = stackVarnode.getSize(); - return var.getDataType().getName() + ":" + pieceLen + " (piece)"; - } - - @Override - public DataType clone(DataTypeManager dtm) { - if (dtm == getDataTypeManager()) { - return this; - } - throw new IllegalArgumentException("May not be cloned with new DataTypeManager"); - } - - @Override - public DataType copy(DataTypeManager dtm) { - throw new UnsupportedOperationException(); - } - - @Override - public void setCategoryPath(CategoryPath path) throws DuplicateNameException { - throw new UnsupportedOperationException(); - } - - @Override - public void setName(String name) throws InvalidNameException { - throw new UnsupportedOperationException(); - } - - @Override - public void setNameAndCategory(CategoryPath path, String name) - throws InvalidNameException, DuplicateNameException { - throw new UnsupportedOperationException(); - } - - @Override - public String getMnemonic(Settings settings) { - DataType dt = variable.getDataType(); - return dt.getMnemonic(settings) + ":" + getLength(); - } - - @Override - public int getLength() { - VariableStorage storage = variable.getVariableStorage(); - Varnode stackVarnode = storage.getLastVarnode(); - return stackVarnode.getSize(); - } - - @Override - public String getDescription() { - // We could provide a description if needed - return null; - } - - @Override - public Object getValue(MemBuffer buf, Settings settings, int length) { - return null; - } - - @Override - public String getRepresentation(MemBuffer buf, Settings settings, int length) { - return null; - } - - @Override - public boolean isEquivalent(DataType dt) { - return false; - } - - @Override - public void dataTypeSizeChanged(DataType dt) { - // ignore - } - - @Override - public void dataTypeDeleted(DataType dt) { - // ignore - } - - @Override - public void dataTypeReplaced(DataType oldDt, DataType newDt) { - // ignore - } - - @Override - public void dataTypeNameChanged(DataType dt, String oldName) { - // ignore - } - - @Override - public boolean dependsOn(DataType dt) { - return false; - } - -} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/AbstractStackEditorProviderTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/AbstractStackEditorProviderTest.java index 2e5b4e6b22..c4796f8a4f 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/AbstractStackEditorProviderTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/AbstractStackEditorProviderTest.java @@ -44,8 +44,6 @@ public abstract class AbstractStackEditorProviderTest extends AbstractStackEdito env.showTool(); } - - //================================================================================================== // Private Methods //================================================================================================== @@ -140,7 +138,7 @@ public abstract class AbstractStackEditorProviderTest extends AbstractStackEdito Parameter parameter = function.getParameter(parameterIndex); SetVariableNameCmd cmd = new SetVariableNameCmd(parameter, newName, SourceType.USER_DEFINED); - applyCmd(program, cmd); + program.withTransaction("Rename Parameter", () -> cmd.applyTo(program)); // avoid blocking return parameter; } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorCellEditTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorCellEditTest.java index 935532393b..4a162b8d25 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorCellEditTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorCellEditTest.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -384,11 +384,29 @@ public class StackEditorCellEditTest extends AbstractStackEditorTest { typeInCellEditor("testName1\n"); assertTrue(applyAction.isEnabled()); - // Change name back and apply disables + // Attempt use of another default name + clickTableCell(getTable(), 0, colNum, 2); + assertIsEditingField(0, colNum); + selectAllInCellEditor(); + typeInCellEditor("local_8\n"); + assertStatus("Cannot set a stack variable name to a default value"); + assertCellString("testName1", 0, colNum); + assertTrue(applyAction.isEnabled()); + + // Change name back to original and apply disables clickTableCell(getTable(), 0, colNum, 2); assertIsEditingField(0, colNum); selectAllInCellEditor(); typeInCellEditor("local_10\n"); + assertCellString("local_10", 0, colNum); + assertTrue(!applyAction.isEnabled()); + + // Change name back to original and apply disables + clickTableCell(getTable(), 0, colNum, 2); + assertIsEditingField(0, colNum); + selectAllInCellEditor(); + typeInCellEditor("\b\n"); // clear entry + assertCellString("local_10", 0, colNum); assertTrue(!applyAction.isEnabled()); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorEnablementTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorEnablementTest.java index 8295ef297e..feb1662494 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorEnablementTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorEnablementTest.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -200,7 +200,7 @@ public class StackEditorEnablementTest extends AbstractStackEditorTest { public void testCentralComponentSelectedEnablement() throws Exception { init(SIMPLE_STACK); - // Check enablement on central component selected. + // Check enablement on central defined-component selected which has undefined datatype runSwing(() -> model.setSelection(new int[] { 1 })); int numBytes = getModel().getMaxReplaceLength(1); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider1Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider1Test.java index 5481593a77..ad17b770a1 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider1Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/stackeditor/StackEditorProvider1Test.java @@ -19,8 +19,7 @@ import static org.junit.Assert.*; import java.awt.Window; -import javax.swing.JTextField; -import javax.swing.SwingUtilities; +import javax.swing.*; import org.junit.Test; @@ -324,6 +323,8 @@ public class StackEditorProvider1Test extends AbstractStackEditorProviderTest { int parameterIndex = 0; Parameter parameter = renameParameterInListing(parameterIndex, "listing.test.name"); + waitForSwing(); + // Verify the provider's name for that parameter is updated String modelParameterName = getParameterNameFromModel(parameterIndex); assertEquals(parameter.getName(), modelParameterName); @@ -342,6 +343,9 @@ public class StackEditorProvider1Test extends AbstractStackEditorProviderTest { // Change the name of a parameter in the Listing renameParameterInListing(parameterIndex, "listing.test.name"); + JDialog reloadDialog = waitForJDialog("Reload Stack Editor?"); + pressButton(reloadDialog, "No"); + // Verify the name of the parameter in the provider is not changed from the original edit String currentModelName = getParameterNameFromModel(parameterIndex); assertEquals(newModelName, currentModelName); @@ -360,6 +364,9 @@ public class StackEditorProvider1Test extends AbstractStackEditorProviderTest { // Change the name of a parameter in the Listing renameParameterInListing(parameterIndex, "listing.test.name"); + JDialog reloadDialog = waitForJDialog("Reload Stack Editor?"); + pressButton(reloadDialog, "No"); + // Press Apply apply(); @@ -386,6 +393,9 @@ public class StackEditorProvider1Test extends AbstractStackEditorProviderTest { String newListingText = "listing.test.name"; renameParameterInListing(parameterIndex, newListingText); + JDialog reloadDialog = waitForJDialog("Reload Stack Editor?"); + pressButton(reloadDialog, "No"); + // Press Apply apply(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java index 7ed48df25c..fba32fb942 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeComponentDB.java @@ -36,8 +36,7 @@ class DataTypeComponentDB implements InternalDataTypeComponent { private final ComponentDBAdapter adapter; private final DBRecord record; // null record -> immutable component private final CompositeDB parent; - - private DataType cachedDataType; // required for bit-fields during packing process + private final DataType cachedDataType; // used by immutable defined component (no record) private int ordinal; private int offset; @@ -56,9 +55,15 @@ class DataTypeComponentDB implements InternalDataTypeComponent { */ DataTypeComponentDB(DataTypeManagerDB dataMgr, CompositeDB parent, int ordinal, int offset, DataType datatype, int length) { - this(dataMgr, parent, ordinal, offset); + this.dataMgr = dataMgr; + this.parent = parent; this.cachedDataType = datatype; + + this.ordinal = ordinal; + this.offset = offset; this.length = length; + this.record = null; + this.adapter = null; } /** @@ -73,6 +78,8 @@ class DataTypeComponentDB implements InternalDataTypeComponent { this.dataMgr = dataMgr; this.parent = parent; this.ordinal = ordinal; + this.cachedDataType = null; + this.offset = offset; this.length = 1; this.record = null; @@ -90,11 +97,13 @@ class DataTypeComponentDB implements InternalDataTypeComponent { DBRecord record) { this.dataMgr = dataMgr; this.adapter = adapter; + this.cachedDataType = null; this.record = record; + this.parent = parent; - ordinal = record.getIntValue(ComponentDBAdapter.COMPONENT_ORDINAL_COL); - offset = record.getIntValue(ComponentDBAdapter.COMPONENT_OFFSET_COL); - length = record.getIntValue(ComponentDBAdapter.COMPONENT_SIZE_COL); + this.ordinal = record.getIntValue(ComponentDBAdapter.COMPONENT_ORDINAL_COL); + this.offset = record.getIntValue(ComponentDBAdapter.COMPONENT_OFFSET_COL); + this.length = record.getIntValue(ComponentDBAdapter.COMPONENT_SIZE_COL); if (isZeroBitFieldComponent()) { length = 0; // previously stored as 1, force to 0 } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/StructureDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/StructureDB.java index e66f2bab54..531adf473a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/StructureDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/StructureDB.java @@ -1378,11 +1378,13 @@ class StructureDB extends CompositeDB implements StructureInternal { * @param componentName name of component replacement (may be null) * @param comment comment for component replacement (may be null) * @return new/updated component (may be null if replacement is not a defined component) + * @throws IllegalArgumentException if unable to identify/make sufficient space * @throws IOException if an IO error occurs */ private DataTypeComponent doComponentReplacement( LinkedList replacedComponents, int offset, DataType dataType, - int length, String componentName, String comment) throws IOException { + int length, String componentName, String comment) + throws IllegalArgumentException, IOException { // Attempt quick update of a single defined component if possible. // A quick update requires that component characteristics including length, offset, @@ -1416,13 +1418,14 @@ class StructureDB extends CompositeDB implements StructureInternal { @Override public final DataTypeComponent replace(int ordinal, DataType dataType, int length) - throws IllegalArgumentException { + throws IllegalArgumentException, IndexOutOfBoundsException { return replace(ordinal, dataType, length, null, null); } @Override public DataTypeComponent replace(int ordinal, DataType dataType, int length, - String componentName, String comment) { + String componentName, String comment) + throws IllegalArgumentException, IndexOutOfBoundsException { lock.acquire(); try { checkDeleted(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionStackFrame.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionStackFrame.java index 023d951e17..ebc2d9464f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionStackFrame.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionStackFrame.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -277,16 +277,18 @@ class FunctionStackFrame implements StackFrame { function.setLocalSize(size); } - /* - * (non-Javadoc) - * @see ghidra.program.model.listing.StackFrame#getParameterSize() - */ @Override public int getParameterSize() { function.manager.lock.acquire(); try { checkIsValid(); + // NOTE: This logic is sensitive to the existance of Local variables at the incorrect + // stack offset placed before parameters. This can occur when adjustments are made to + // the prototype model's stack pentry specification. Unfortunately, the distinction + // between a parameter and a local is locked-in at the time of creation due to the + // use of distinct symbol types. + int baseOffset = 0; Integer base = VariableUtilities.getBaseStackParamOffset(function); if (base != null) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Structure.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Structure.java index 41b896873d..4a28743832 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Structure.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Structure.java @@ -44,7 +44,8 @@ public interface Structure extends Composite { public DataTypeComponent getComponent(int ordinal) throws IndexOutOfBoundsException; /** - * Gets the first defined component located at or after the specified offset. + * Gets the first defined component located at or after the specified offset. If a + * component contains the specified offset that component will be returned. * Note: The returned component may be a zero-length component. * * @param offset the byte offset into this structure @@ -436,7 +437,7 @@ public interface Structure extends Composite { * @throws IllegalArgumentException if amount < 0 */ public void growStructure(int amount); - + /** * Set the size of the structure to the specified byte-length. If the length is shortened defined * components will be cleared and removed as required. diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/StackFrame.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/StackFrame.java index 236c0b68de..a24263e8d3 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/StackFrame.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/StackFrame.java @@ -107,13 +107,6 @@ public interface StackFrame { */ public int getParameterOffset(); -// /** -// * Set the offset on the stack of the parameters. -// * -// * @param offset the start offset of parameters on the stack -// */ -// public void setParameterOffset(int offset) throws InvalidInputException; - /** * Returns true if specified offset could correspond to a parameter * @param offset