diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsAdapter.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsAdapter.java index d550ba9279..21353d9a82 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsAdapter.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsAdapter.java @@ -48,7 +48,7 @@ public class DBTraceDataSettingsAdapter static final String NAME_COLUMN_NAME = "Name"; static final String LONG_VALUE_COLUMN_NAME = "LongValue"; static final String STRING_VALUE_COLUMN_NAME = "StringValue"; - static final String BYTES_VALUE_COLUMN_NAME = "BytesValue"; + static final String BYTES_VALUE_COLUMN_NAME = "BytesValue"; // NOTE: Requirement dropped from Program API @DBAnnotatedColumn(NAME_COLUMN_NAME) static DBObjectColumn NAME_COLUMN; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsOperations.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsOperations.java index e62c83136d..c1397fb84d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsOperations.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsOperations.java @@ -120,19 +120,6 @@ public interface DBTraceDataSettingsOperations } } - default void setBytes(Range lifespan, Address address, String name, byte[] value) { - try (LockHold hold = LockHold.lock(getLock().writeLock())) { - doExactOrNew(lifespan, address, name).setBytes(value); - } - } - - default byte[] getBytes(long snap, Address address, String name) { - try (LockHold hold = LockHold.lock(getLock().readLock())) { - DBTraceSettingsEntry entry = doGetEntry(snap, address, name); - return entry == null ? null : entry.getBytes(); - } - } - default void setValue(Range lifespan, Address address, String name, Object value) { assertKnownType(value); try (LockHold hold = LockHold.lock(getLock().writeLock())) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataTypeManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataTypeManager.java index 5d5ab618d9..64483bcb8f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataTypeManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataTypeManager.java @@ -21,7 +21,8 @@ import java.util.concurrent.locks.ReadWriteLock; import db.DBHandle; import ghidra.framework.model.DomainFile; -import ghidra.program.database.data.DataTypeManagerDB; +import ghidra.program.database.data.ProgramBasedDataTypeManagerDB; +import ghidra.program.model.address.Address; import ghidra.program.model.data.*; import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTraceManager; @@ -33,7 +34,7 @@ import ghidra.util.exception.CancelledException; import ghidra.util.exception.VersionException; import ghidra.util.task.TaskMonitor; -public class DBTraceDataTypeManager extends DataTypeManagerDB +public class DBTraceDataTypeManager extends ProgramBasedDataTypeManagerDB implements TraceBasedDataTypeManager, DBTraceManager { protected final ReadWriteLock lock; @@ -43,10 +44,20 @@ public class DBTraceDataTypeManager extends DataTypeManagerDB TaskMonitor monitor, DBTrace trace) throws CancelledException, VersionException, IOException { super(dbh, null, openMode.toInteger(), trace, trace.getLock(), monitor); - this.lock = lock; + this.lock = lock; // TODO: nothing uses this local lock - not sure what its purpose is this.trace = trace; } + @Override + protected void dataSettingChanged(Address address) { + // ignored - instance settings are not current supported (no AddressMap provided) + } + + @Override + public boolean allowsDefaultBuiltInSettings() { + return true; + } + @Override public void invalidateCache(boolean all) { super.invalidateCache(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataAdapter.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataAdapter.java index 8cb156ba89..34e2273989 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataAdapter.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataAdapter.java @@ -119,31 +119,6 @@ public interface DBTraceDataAdapter extends DBTraceCodeUnitAdapter, DataAdapterM } - @Override - default void setByteArray(String name, byte[] value) { - try (LockHold hold = getTrace().lockWrite()) { - getSettingsSpace(true).setBytes(getLifespan(), getAddress(), name, value); - } - getTrace().setChanged(new TraceChangeRecord<>( - TraceCodeChangeType.DATA_TYPE_SETTINGS_CHANGED, getTraceSpace(), this.getBounds(), null, - null)); - } - - @Override - default byte[] getByteArray(String name) { - try (LockHold hold = getTrace().lockRead()) { - DBTraceDataSettingsOperations space = getSettingsSpace(false); - if (space != null) { - byte[] value = space.getBytes(getStartSnap(), getAddress(), name); - if (value != null) { - return value; - } - } - Settings defaultSettings = getDefaultSettings(); - return defaultSettings == null ? null : defaultSettings.getByteArray(name); - } - } - @Override default void setValue(String name, Object value) { try (LockHold hold = getTrace().lockWrite()) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/listing/DBTraceCodeUnitTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/listing/DBTraceCodeUnitTest.java index a0e9d06fc0..a2f342e9fb 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/listing/DBTraceCodeUnitTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/listing/DBTraceCodeUnitTest.java @@ -1533,7 +1533,7 @@ public class DBTraceCodeUnitTest extends AbstractGhidraHeadlessIntegrationTest Settings defs = myTypedef.getDefaultSettings(); defs.setLong("myDefaultLong", 0x123456789L); defs.setString("myDefaultString", "Hello!"); - defs.setByteArray("myDefaultBytes", new byte[] { 4, 3, 2, 1 }); + //defs.setByteArray("myDefaultBytes", new byte[] { 4, 3, 2, 1 }); assertTrue(d4000.isEmpty()); // This is a terribly counter-intuitive method name assertArrayEquals(new String[] {}, d4000.getNames()); @@ -1546,23 +1546,23 @@ public class DBTraceCodeUnitTest extends AbstractGhidraHeadlessIntegrationTest assertNull(u3fff.getLong("myLong")); assertNull(d4000.getLong("myLong")); assertNull(d4000.getString("myString")); - assertNull(d4000.getByteArray("myBytes")); + //assertNull(d4000.getByteArray("myBytes")); assertNull(d4000.getValue("myLong")); assertFalse(d4000.isConstant()); assertFalse(d4000.isVolatile()); assertEquals(0x123456789L, d4000.getLong("myDefaultLong").longValue()); assertEquals("Hello!", d4000.getString("myDefaultString")); - assertArrayEquals(new byte[] { 4, 3, 2, 1 }, d4000.getByteArray("myDefaultBytes")); + //assertArrayEquals(new byte[] { 4, 3, 2, 1 }, d4000.getByteArray("myDefaultBytes")); assertEquals("Hello!", d4000.getValue("myDefaultString")); d4000.setLong("myLong", Long.MAX_VALUE); d4000.setString("myString", "Good bye!"); - d4000.setByteArray("myBytes", new byte[] { 8, 7, 6, 5 }); + //d4000.setByteArray("myBytes", new byte[] { 8, 7, 6, 5 }); assertFalse(d4000.isEmpty()); // TODO: Figure out whether or not this includes defaultSettings? - assertEquals(Set.of("myLong", "myString", "myBytes"), set(d4000.getNames())); + assertEquals(Set.of("myLong", "myString" /*, "myBytes"*/), set(d4000.getNames())); d4000.setLong("myDefaultLong", Long.MAX_VALUE); d4000.setString("myDefaultString", "Good bye!"); @@ -1570,12 +1570,13 @@ public class DBTraceCodeUnitTest extends AbstractGhidraHeadlessIntegrationTest assertEquals(Long.MAX_VALUE, d4000.getLong("myLong").longValue()); assertEquals("Good bye!", d4000.getString("myString")); - assertArrayEquals(new byte[] { 8, 7, 6, 5 }, d4000.getByteArray("myBytes")); - assertArrayEquals(new byte[] { 8, 7, 6, 5 }, (byte[]) d4000.getValue("myBytes")); + //assertArrayEquals(new byte[] { 8, 7, 6, 5 }, d4000.getByteArray("myBytes")); + //assertArrayEquals(new byte[] { 8, 7, 6, 5 }, (byte[]) d4000.getValue("myBytes")); assertEquals(Long.MAX_VALUE, d4000.getLong("myDefaultLong").longValue()); assertEquals("Good bye!", d4000.getString("myDefaultString")); - assertArrayEquals(new byte[] { 8, 7, 6, 5 }, d4000.getByteArray("myDefaultBytes")); + //assertArrayEquals(new byte[] { 8, 7, 6, 5 }, d4000.getByteArray("myDefaultBytes")); + assertArrayEquals(new byte[] { 8, 7, 6, 5 }, (byte[]) d4000.getValue("myDefaultBytes")); d4000.clearSetting("myDefaultLong"); assertEquals(0x123456789L, d4000.getLong("myDefaultLong").longValue()); @@ -1587,11 +1588,11 @@ public class DBTraceCodeUnitTest extends AbstractGhidraHeadlessIntegrationTest assertNull(d4000.getLong("myLong")); assertNull(d4000.getString("myString")); - assertNull(d4000.getByteArray("myBytes")); + //assertNull(d4000.getByteArray("myBytes")); assertEquals(0x123456789L, d4000.getLong("myDefaultLong").longValue()); assertEquals("Hello!", d4000.getString("myDefaultString")); - assertArrayEquals(new byte[] { 4, 3, 2, 1 }, d4000.getByteArray("myDefaultBytes")); + //assertArrayEquals(new byte[] { 4, 3, 2, 1 }, d4000.getByteArray("myDefaultBytes")); assertNull(d4000.getValue("myLong")); assertFalse(d4000.isConstant()); diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm index e186f9b1a5..103809ad82 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm @@ -218,11 +218,11 @@ Settings @@ -294,6 +294,18 @@ Name Signed/Unsigned + + char + (determined by data organziation)* + + + schar + Signed* + + + uchar + Unsigned* + short Signed @@ -332,11 +344,16 @@ Settings (may vary) + *Additional character type Settings: + @@ -360,17 +377,9 @@ Name Description - - char - Signed ASCII character - - - uchar - Unsigned ASCII character - wchar_t - Signed Wide Character + Signed Wide Character* pointer @@ -391,9 +400,9 @@ - Settings + Settings (may vary) @@ -483,7 +492,7 @@ Settings @@ -770,7 +779,7 @@ press OK.  To reset a setting to its default value, set the Use Default checkbox.

-

The default settings for a given data type can +

The default settings for a given data type can be changed.  When a default setting has been changed, every data item currently using the default setting for that data type will use the new default value.  Data items that have a modified value for that setting will not be affected.

@@ -1173,9 +1182,14 @@

A typedef is an alias for another data type. It is useful for giving a more meaningful name to a data type.  For example, you might typedef dword to be - int.  Typedefs are created - using the Data Type Manager and applied like any other data type.

+ int.  + In addition, a typedef may be based upon a pointer with additional Settings which can influence + how such a pointer should be interpretted. + Typedefs are created + using the Data Type Manager and applied like any other data type (See + Creating New + User Defined Data Types).

+

Void

diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm index 0f16964181..715387aaad 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm @@ -785,24 +785,24 @@ signature editor for function definitions) for creating the new desired data type.

+

Creating a Typedef

Creating a new typedef is even easier. Right-click on the data type to be typedef'ed and select the New Typedef on XYZ action. A new typedef will be created on the XYZ data type in the same category as the original data type.

-
-

- Alternatively, you can click New +

Alternatively, you can click New Typedef..., which will show a dialog that allows you to choose a typedef name and the data type from which the typedef will be created.* This action can also be executed from any folder instead of directly on another data type.

-
-
-
+ +

A Typedef created with a Pointer base type will allow additional Settings to be made + which can influence how such a pointer should be interpretted + (see Pointer-Typedef Settings).

- +

Creating a Pointer

To create a pointer, you can click New Pointer to XYZ. A new pointer will be created to the XYZ data type in the same category as the original data type.*

@@ -1056,8 +1056,53 @@ part of your tool's state when you close the Project or exit Ghidra. Your list of favorites is restored when you re-open your project or restart Ghidra.

+ +

Pointer-Typedef Settings

+ +
+

On occasion there may be the need to add stipulate additional attributes on a pointer + type to stipulate how the associated pointer should be interpreted or processed during analysis. + Such pointer attributes may only be specified when such a pointer in in the form of a Typedef + which enables the datatype to preserve these attributes during type resolution and propogation. + This includes preservation of such Typedef Settings within a data type archive, and through + merge processing, which normal Data Settings do not support.

+ +

The Typedef Settings may be modified via the Settings dialog in the same way data type Default Settings + are changed for listing Data (see Changing Default Settings for Data). + In addition to this popup action on listing Data, the dialog may be displayed from the Data Type Manager + tree by right-clicking on a data type and selecting the Settings... action. Typedef-specific settings + are only supported as default settings for a typedef and may not be overriden at the component or data level. + +

The following Pointer-Typedef settings are currently supported:

+
    +
  • Address Space (case-sensitive string) - Allows a specific address space to be associated with a pointer. + If an unknown name is used it will be silently ignored.
  • +
  • Component Offset (signed value) - Allows a base-relative offset to be specified. When applied + to memory an Offset Reference will be generated. I addition, type analysis may use the offset to identify + a component relative to the pointer's base-datatype (e.g., structure).
  • +
  • Offset Mask (64-bit mask) - Allows a bit-mask to be applied to a stored value when computing the + absolute memory offset. This bit-mask will be applied prior to any applied bit-shift.
  • +
  • Offset Shift (-64..0..64) - Allows a bit-shift (left=negative, right=positive) to be applied to a + stored value when computing the absolute memory offset.
  • +
  • Pointer Type (default, IBO, relative, file-offset) - allows the overall interpretation of a + pointer to be specified. The relative pointer type has limited applicabaility and is only + intended to be applied to pointers stored in memory since their storage location is used in computing + the absolute address that the pointer refers to. (IBO: Image Base Offset Relative).
  • +
+ +

All Typedef Settings must be established on + a Typedef before such a type is applied to Data or referenced by other types. This is highly recommended + since the side-affects of using such a modified typedef will not be updated to reflect subsequent changes. +

+ +

Full support for the above Pointer-Typedef + Settings within analysis and decompilation will evolve over time. We also hope to improve + naming concerns for such typedefs and to replace the use of custom BuiltIn data types which would + be better modeled as a Pointer.

+ +
- +

Provided by: DataTypeManagerPlugin

diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Glossary/glossary.htm b/Ghidra/Features/Base/src/main/help/help/topics/Glossary/glossary.htm index 14170b74b3..93025be9bf 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/Glossary/glossary.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/Glossary/glossary.htm @@ -664,7 +664,7 @@ xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-

Display options for individual data items in Ghidra. For example one byte can be displayed as decimal while another is displayed as hex.  Also see Data Type Default Settings.

+ "help/topics/DataPlugin/Data.htm#Default_Settings">Data Type Default Settings.

Instruction

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypeMergeManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypeMergeManager.java index 9ca5869e6b..84b6f91a50 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypeMergeManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypeMergeManager.java @@ -1791,9 +1791,6 @@ public class DataTypeMergeManager implements MergeResolver { Composite c2 = (Composite) dt2; return compositeDataTypeWasChanged(c1, c2); } - if (dt1 instanceof TypeDef) { - return false; - } return !dt1.isEquivalent(dt2); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypePanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypePanel.java index 30a42453f5..66b337e14e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypePanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/datatypes/DataTypePanel.java @@ -24,6 +24,8 @@ import javax.swing.JTextPane; import javax.swing.text.*; import ghidra.app.merge.MergeConstants; +import ghidra.docking.settings.Settings; +import ghidra.docking.settings.SettingsDefinition; import ghidra.program.model.data.*; import ghidra.program.model.data.Enum; import ghidra.program.model.listing.FunctionSignature; @@ -76,6 +78,9 @@ class DataTypePanel extends JPanel { else { formatDataType(dataType); } + if (dataType != null) { + formatDataTypeSettings(dataType); + } textPane.setCaretPosition(0); } @@ -318,7 +323,23 @@ class DataTypePanel extends JPanel { formatPath(td); insertString(td.getDisplayName(), nameAttrSet); insertString("\n", contentAttrSet); - insertString(" TypeDef on " + td.getDataType().getDisplayName(), contentAttrSet); + insertString(" TypeDef on " + td.getDataType().getDisplayName(), contentAttrSet); + } + + private void formatDataTypeSettings(DataType dt) { + Settings settings = dt.getDefaultSettings(); + SettingsDefinition[] defs = + SettingsDefinition.filterSettingsDefinitions(dt.getSettingsDefinitions(), def -> { + return (def instanceof TypeDefSettingsDefinition) && def.hasValue(settings); + }); + if (defs.length == 0) { + return; + } + insertString("\n\nSettings\n", sourceAttrSet); + for (SettingsDefinition def : defs) { + insertString(" " + def.getName() + ": " + def.getValueString(settings) + "\n", + contentAttrSet); + } } private void formatFunctionDef(FunctionDefinition fd) { 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 8a8c9ab7c7..b9a6df69e2 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 @@ -51,7 +51,7 @@ public abstract class CompEditorModel extends CompositeEditorModel { */ @Override public void load(Composite dataType) { - super.load(dataType, true); + super.load(dataType); fixSelection(); selectionChanged(); } @@ -116,11 +116,12 @@ public abstract class CompEditorModel extends CompositeEditorModel { } originalDt.setDescription(getDescription()); replaceOriginalComponents(); - load(originalDt, true); + updateOriginalComponentSettings(viewComposite, originalDt); + load(originalDt); } else { Composite dt = (Composite) originalDTM.resolve(viewComposite, null); - load(dt, true); + load(dt); } return true; } @@ -1310,81 +1311,88 @@ public abstract class CompEditorModel extends CompositeEditorModel { public void dataTypeChanged(DataTypeManager dtm, DataTypePath path) { try { - if (isLoaded()) { - // If we don't currently have any modifications that need applying and - // the structure in the editor just changed, then show the changed - // structure. - if (originalDataTypePath == null) { + if (dtm instanceof CompositeViewerDataTypeManager) { + // required to detect settings changes + componentEdited(); + return; + } + + DataTypeManager originalDTM = getOriginalDataTypeManager(); + if (dtm != originalDTM) { + return; // Different DTM than the one for this data type. + } + + if (!isLoaded()) { + return; + } + + // If we don't currently have any modifications that need applying and + // the structure in the editor just changed, then show the changed + // structure. + String oldName = path.getDataTypeName(); + if (path.equals(originalDataTypePath)) { + if (consideringReplacedDataType) { return; } - String oldName = path.getDataTypeName(); - if (path.equals(originalDataTypePath)) { - if (consideringReplacedDataType) { - return; - } - // Return if the original is already changing. Need this since there - // can be multiple change notifications from a single data type update - // event due to replaceWith(), setLastChangeTime() and - // setLastChangeTimeInSource() each firing dataTypeChanged(). - if (originalIsChanging) { - return; - } - originalIsChanging = true; - try { - if (hadChanges) { - String message = "" + HTMLUtilities.escapeHTML(oldName) + - " has changed outside the editor.
" + - "Discard edits & reload the " + getTypeName() + "?"; - String title = "Reload " + getTypeName() + " Editor?"; - int response = OptionDialog.showYesNoDialogWithNoAsDefaultButton( - provider.getComponent(), title, message); - if (response == OptionDialog.OPTION_ONE) { - load(getOriginalComposite()); - } - originalComponentsChanged(); - } - else { - Composite changedComposite = getOriginalComposite(); - if ((changedComposite != null) && - !viewComposite.isEquivalent(changedComposite)) { - load(getOriginalComposite()); - setStatus( - viewComposite.getPathName() + " changed outside the editor.", - false); - } + // Return if the original is already changing. Need this since there + // can be multiple change notifications from a single data type update + // event due to replaceWith(), setLastChangeTime() and + // setLastChangeTimeInSource() each firing dataTypeChanged(). + if (originalIsChanging) { + return; + } + originalIsChanging = true; + try { + if (hadChanges) { + String message = "" + HTMLUtilities.escapeHTML(oldName) + + " has changed outside the editor.
" + "Discard edits & reload the " + + getTypeName() + "?"; + String title = "Reload " + getTypeName() + " Editor?"; + int response = OptionDialog.showYesNoDialogWithNoAsDefaultButton( + provider.getComponent(), title, message); + if (response == OptionDialog.OPTION_ONE) { + load(getOriginalComposite()); } } - finally { - originalIsChanging = false; + else { + Composite changedComposite = getOriginalComposite(); + if ((changedComposite != null) && + !viewComposite.isEquivalent(changedComposite)) { + load(getOriginalComposite()); + setStatus(viewComposite.getPathName() + " changed outside the editor.", + false); + } } } - else { - DataType viewDt = viewDTM.getDataType(path); - if (viewDt == null) { - return; - } - int origDtLen = viewDt.getLength(); - DataType changedDt = dtm.getDataType(path); - if (changedDt != null) { - if ((viewDt instanceof Composite) && (changedDt instanceof Composite)) { - Composite comp = (Composite) changedDt; - Composite origDt = getOriginalComposite(); - if ((origDt != null) && comp.isPartOf(origDt)) { - removeDtFromComponents(comp); - } + finally { + originalIsChanging = false; + } + } + else { + DataType viewDt = viewDTM.getDataType(path); + if (viewDt == null) { + return; + } + int origDtLen = viewDt.getLength(); + DataType changedDt = dtm.getDataType(path); + if (changedDt != null) { + if ((viewDt instanceof Composite) && (changedDt instanceof Composite)) { + Composite comp = (Composite) changedDt; + Composite origDt = getOriginalComposite(); + if ((origDt != null) && comp.isPartOf(origDt)) { + removeDtFromComponents(comp); + } - ((Composite) viewDt).setDescription( - ((Composite) changedDt).getDescription()); - } - viewDt = - viewDTM.resolve(changedDt, DataTypeConflictHandler.REPLACE_HANDLER); - if (origDtLen != viewDt.getLength()) { - viewComposite.dataTypeSizeChanged(viewDt); - } + ((Composite) viewDt) + .setDescription(((Composite) changedDt).getDescription()); + } + viewDt = viewDTM.resolve(changedDt, DataTypeConflictHandler.REPLACE_HANDLER); + if (origDtLen != viewDt.getLength()) { + viewComposite.dataTypeSizeChanged(viewDt); } - fireTableDataChanged(); - componentDataChanged(); } + fireTableDataChanged(); + componentDataChanged(); } } catch (ConcurrentModificationException e) { @@ -1397,78 +1405,74 @@ public abstract class CompEditorModel extends CompositeEditorModel { @Override public void dataTypeReplaced(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath, DataType newDataType) { - if (newDataType == null) { + + DataTypeManager originalDTM = getOriginalDataTypeManager(); + if (dtm != originalDTM) { + return; // Different DTM than the one for this data type. + } + + if (!isLoaded()) { return; } - if (isLoaded()) { - DataTypeManager originalDataTypeManager = getOriginalDataTypeManager(); - if (originalDataTypeManager != dtm) { - return; - } - if (originalDataTypePath == null) { - return; - } - String dtName = oldPath.getDataTypeName(); - DataTypePath dtPath = new DataTypePath(newDataType.getCategoryPath(), dtName); - if (!dtPath.equals(originalDataTypePath)) { - DataType dt = viewDTM.getDataType(dtPath); - if (dt != null) { - if (hasSubDt(viewComposite, dtPath)) { - String msg = "Replaced data type \"" + dtPath + - "\", which is a sub-component of \"" + getOriginalDataTypeName() + - "\"."; - setStatus(msg, true); - } - // NOTE: depending upon event sequence and handling a - // re-load may have occured and replcement may be uneccessary - try { - viewDTM.replaceDataType(dt, newDataType, true); - } - catch (DataTypeDependencyException e) { - throw new AssertException(e); - } - fireTableDataChanged(); - componentDataChanged(); + + String dtName = oldPath.getDataTypeName(); + DataTypePath dtPath = new DataTypePath(newDataType.getCategoryPath(), dtName); + if (!dtPath.equals(originalDataTypePath)) { + DataType dt = viewDTM.getDataType(dtPath); + if (dt != null) { + if (hasSubDt(viewComposite, dtPath)) { + String msg = "Replaced data type \"" + dtPath + + "\", which is a sub-component of \"" + getOriginalDataTypeName() + "\"."; + setStatus(msg, true); } + // NOTE: depending upon event sequence and handling a + // re-load may have occured and replcement may be uneccessary + try { + viewDTM.replaceDataType(dt, newDataType, true); + } + catch (DataTypeDependencyException e) { + throw new AssertException(e); + } + fireTableDataChanged(); + componentDataChanged(); } - else { - if (this.hadChanges) { - if (originalDataTypePath.equals(oldPath)) { - if (hadChanges) { - consideringReplacedDataType = true; - try { - String message = - "" + HTMLUtilities.escapeHTML(oldPath.getPath()) + - " has changed outside the editor.
" + - "Discard edits & reload the " + getTypeName() + "?"; - String title = "Reload " + getTypeName() + " Editor?"; - int response = OptionDialog.showYesNoDialogWithNoAsDefaultButton( - provider.getComponent(), title, message); - if (response == OptionDialog.OPTION_ONE) { - load(getOriginalComposite()); - } - originalComponentsChanged(); - } - finally { - consideringReplacedDataType = false; + } + else { + if (this.hadChanges) { + if (originalDataTypePath.equals(oldPath)) { + if (hadChanges) { + consideringReplacedDataType = true; + try { + String message = + "" + HTMLUtilities.escapeHTML(oldPath.getPath()) + + " has changed outside the editor.
" + + "Discard edits & reload the " + getTypeName() + "?"; + String title = "Reload " + getTypeName() + " Editor?"; + int response = OptionDialog.showYesNoDialogWithNoAsDefaultButton( + provider.getComponent(), title, message); + if (response == OptionDialog.OPTION_ONE) { + load(getOriginalComposite()); } } - else { - load(getOriginalComposite()); - setStatus(viewComposite.getPathName() + " changed outside the editor.", - false); + finally { + consideringReplacedDataType = false; } } else { - String msg = "\"" + oldPath.getPath() + "\" was replaced with " + - newDataType.getPathName() + " in the data type manager."; - setStatus(msg, true); + load(getOriginalComposite()); + setStatus(viewComposite.getPathName() + " changed outside the editor.", + false); } } else { - load((Composite) newDataType); + String msg = "\"" + oldPath.getPath() + "\" was replaced with " + + newDataType.getPathName() + " in the data type manager."; + setStatus(msg, true); } } + else { + load((Composite) newDataType); + } } } @@ -1701,9 +1705,6 @@ public abstract class CompEditorModel extends CompositeEditorModel { public int getActualAlignment() { return viewComposite.getAlignment(); -// return viewDTM.getDataOrganization().getAlignment(viewComposite, getLength()); } - - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ComponentContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ComponentContext.java new file mode 100644 index 0000000000..1e85ee7ffa --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ComponentContext.java @@ -0,0 +1,43 @@ +/* ### + * 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.compositeeditor; + +import ghidra.program.model.data.*; + +/** + * ComponentContext provides a selected component context when editing a structure/union + */ +public interface ComponentContext { + + /** + * Get editor's data type manager + * @return editor's datatype manager + */ + DataTypeManager getDataTypeManager(); + + /** + * Get the editor's selected component's parent composite (structure or union) + * @return editor's selected component's parent composite + */ + Composite getCompositeDataType(); + + /** + * Get the editor's selected component + * @return editor's selected component + */ + DataTypeComponent getDataTypeComponent(); + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ComponentProgramActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ComponentProgramActionContext.java new file mode 100644 index 0000000000..a2411f58ea --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ComponentProgramActionContext.java @@ -0,0 +1,61 @@ +/* ### + * 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.compositeeditor; + +import ghidra.app.context.ProgramActionContext; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.Program; + +/** + * ComponentProgramActionContext provides an action context when editing a + * composite with a single selected component, and the composite is associated with a program. + */ +public class ComponentProgramActionContext extends ProgramActionContext + implements ComponentContext { + + private DataTypeComponent component; + private Composite composite; + + public ComponentProgramActionContext(CompositeEditorProvider compositeEditorProvider, + Program program, DataTypeComponent component) { + super(compositeEditorProvider, program); + this.component = component; + DataType parent = component.getParent(); + if (!(parent instanceof Composite)) { + throw new IllegalArgumentException("Only Composite components allowed"); + } + this.composite = (Composite) parent; + if (parent.getDataTypeManager() == null) { + throw new IllegalArgumentException("Component's parent must have a DataTypeManager"); + } + } + + @Override + public DataTypeManager getDataTypeManager() { + // Pass target datatype manager - not intended to be modified! + return program.getDataTypeManager(); + } + + @Override + public Composite getCompositeDataType() { + return composite; + } + + @Override + public DataTypeComponent getDataTypeComponent() { + return component; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ComponentStandAloneActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ComponentStandAloneActionContext.java new file mode 100644 index 0000000000..d1fb0a4323 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/ComponentStandAloneActionContext.java @@ -0,0 +1,60 @@ +/* ### + * 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.compositeeditor; + +import docking.ActionContext; +import ghidra.program.model.data.*; + +/** + * ComponentStandAloneActionContext provides an action context when editing a + * composite with a single selected component, and the composite is associated with a + * stand-alone archive. + */ +public class ComponentStandAloneActionContext extends ActionContext + implements ComponentContext { + + private DataTypeComponent component; + private Composite composite; + + public ComponentStandAloneActionContext(CompositeEditorProvider compositeEditorProvider, + DataTypeComponent component) { + super(compositeEditorProvider); + this.component = component; + DataType parent = component.getParent(); + if (!(parent instanceof Composite)) { + throw new IllegalArgumentException("Only Composite components allowed"); + } + this.composite = (Composite) parent; + if (parent.getDataTypeManager() == null) { + throw new IllegalArgumentException("Component's parent must have a DataTypeManager"); + } + } + + @Override + public DataTypeManager getDataTypeManager() { + return composite.getDataTypeManager(); + } + + @Override + public Composite getCompositeDataType() { + return composite; + } + + @Override + public DataTypeComponent getDataTypeComponent() { + return component; + } +} 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 afede9375e..563c02349b 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 @@ -15,7 +15,7 @@ */ package ghidra.app.plugin.core.compositeeditor; -import java.util.ArrayList; +import java.util.*; import docking.widgets.dialogs.NumberInputDialog; import docking.widgets.fieldpanel.support.FieldRange; @@ -31,6 +31,7 @@ import docking.widgets.fieldpanel.support.FieldSelection; */ import ghidra.app.plugin.core.datamgr.util.DataTypeUtils; +import ghidra.docking.settings.Settings; import ghidra.program.model.data.*; import ghidra.program.model.data.Enum; import ghidra.util.*; @@ -58,7 +59,6 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen protected int lastNumElements = 1; protected int lastNumBytes = 1; - private boolean offline = true; protected boolean hadChanges = false; protected boolean originalIsChanging = false; @@ -68,34 +68,27 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen super(provider); } - @Override - public void load(Composite dataType, boolean useOffLineCategory) { - this.offline = useOffLineCategory; - if (dataType == null) { + /** + * Loads the specified composite into the model replacing + * whatever composite is there. + * + * @param dataType the new composite data type. + */ + public void load(Composite dataType) { + if (dataType == null) { // TODO: Why is this needed? Use case? return; -// throw new NullPointerException(); } DataTypeManager dataTypeManager = dataType.getDataTypeManager(); if (dataTypeManager == null) { throw new IllegalArgumentException( "Datatype " + dataType.getName() + " doesn't have a data type manager specified."); } - CategoryPath categoryPath = dataType.getCategoryPath(); - Category cat = dataTypeManager.getCategory(categoryPath); - if (cat == null && !useOffLineCategory) { - throw new IllegalArgumentException( - "Datatype " + dataType.getName() + " category not found: " + - categoryPath.getPath()); - } + if (isEditingField()) { endFieldEditing(); } - DataTypeManager originalDTM = getOriginalDataTypeManager(); + if (isLoaded()) { - // No longer want to listen for changes to previous category. - if (originalDTM != null) { - originalDTM.removeDataTypeManagerListener(this); - } unload(); } @@ -103,32 +96,54 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen originalComposite = dataType; originalDataTypePath = originalComposite.getDataTypePath(); currentName = dataType.getName(); - originalDTM = dataTypeManager; - if (useOffLineCategory) { - viewDTM = new CompositeViewerDataTypeManager(originalDTM.getName(), dataType); - viewComposite = (Composite) viewDTM.resolve(dataType, null); - } - else { - viewDTM = originalDTM; - viewComposite = (Composite) dataType.clone(viewDTM); - } + DataTypeManager originalDTM = dataTypeManager; + + viewComposite = createViewCompositeFromOriginalComposite(originalComposite); + viewDTM = viewComposite.getDataTypeManager(); + // Listen so we can update editor if name changes for this structure. + originalCompositeId = DataTypeManager.NULL_DATATYPE_ID; if (originalDTM.contains(dataType)) { - compositeID = originalDTM.getID(dataType); // Get the id if editing an existing data type. + originalCompositeId = originalDTM.getID(dataType); // Get the id if editing an existing data type. } originalDTM.addDataTypeManagerListener(this); hadChanges = false; setSelection(new FieldSelection()); clearStatus(); - originalNameChanged(); - originalCategoryChanged(); compositeInfoChanged(); fireTableDataChanged(); componentDataChanged(); editorStateChanged(CompositeEditorModelListener.COMPOSITE_LOADED); } + + /** + * Create view composite with the appropriate datatype manager and + * changes listener(s) if required. + * + * @param original original composite being loaded + * @return view composite to used by model + */ + protected Composite createViewCompositeFromOriginalComposite(Composite original) { + + // Use temporary standalone view datatype manager + DataTypeManager dtm = + new CompositeViewerDataTypeManager(original.getDataTypeManager().getName(), original); + Composite composite = (Composite) dtm.resolve(original, null); + + // Clone all settings some of which do not get resolved. + + // NOTE: It is important to note that the editor will allow modification of component + // default settings, however the underlying datatype default settings may not get copied + // as they get resolved into the view datatype manager. This may result in the incorrect + // underlying datatype default setting value being presented when adjusting component + // default settings. + cloneAllComponentSettings(original, composite); + + dtm.addDataTypeManagerListener(this); // listen to view datatype manager changes + return composite; + } @Override public void dispose() { @@ -544,22 +559,6 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen return validName; } - /** - * Returns true if this composite editor model is editing the composite in - * an offline data type manager instance. In other words, changes to the data type - * being edited don't directly affect the original data type manager is unaffected - * until editor changes are applied. - * - *

If this returns false, then the editor directly affects the original - * data type manager. For example, as data types are added to the composite data type, - * they are also added to the original data type manager if not already there. - * - * @return true if editing offline - */ - public boolean isOffline() { - return offline; - } - @Override public boolean hasChanges() { return hadChanges; @@ -582,6 +581,7 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen boolean noCompChanges = false; if (oldComposite != null) { noCompChanges = (viewComposite.isEquivalent(oldComposite) && + hasSameComponentSettings(viewComposite, oldComposite) && !hasCompPathNameChanges(viewComposite, oldComposite)); } else { @@ -591,6 +591,74 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen return hadChanges; } + private boolean hasSameComponentSettings(Composite currentViewComposite, + Composite oldComposite) { + DataTypeComponent[] viewComps = currentViewComposite.getDefinedComponents(); + DataTypeComponent[] oldComps = oldComposite.getDefinedComponents(); + if (viewComps.length != oldComps.length) { + return false; + } + for (int i = 0; i < viewComps.length; i++) { + if (!hasSameSettings(viewComps[i], oldComps[i])) { + return false; + } + } + return true; + } + + private boolean hasSameSettings(DataTypeComponent viewDtc, DataTypeComponent oldDtc) { + Settings viewDtcSettings = viewDtc.getDefaultSettings(); + Settings oldDtcSettings = oldDtc.getDefaultSettings(); + String[] viewSettingsNames = viewDtcSettings.getNames(); + String[] oldSettingsNames = oldDtcSettings.getNames(); + if (viewSettingsNames.length != oldSettingsNames.length) { + return false; + } + Arrays.sort(viewSettingsNames); + Arrays.sort(oldSettingsNames); + if (!Arrays.equals(viewSettingsNames, oldSettingsNames)) { + return false; + } + for (String name : viewSettingsNames) { + if (!Objects.equals(viewDtcSettings.getValue(name), oldDtcSettings.getValue(name))) { + return false; + } + } + return true; + } + + private void cloneAllComponentSettings(Composite sourceComposite, Composite destComposite) { + DataTypeComponent[] sourceComps = sourceComposite.getDefinedComponents(); + DataTypeComponent[] destComps = destComposite.getDefinedComponents(); + assert (sourceComps.length == destComps.length); + for (int i = 0; i < sourceComps.length; i++) { + Settings sourceDtcSettings = sourceComps[i].getDefaultSettings(); + Settings destDtcSettings = destComps[i].getDefaultSettings(); + destDtcSettings.clearAllSettings(); + for (String name : sourceDtcSettings.getNames()) { + destDtcSettings.setValue(name, sourceDtcSettings.getValue(name)); + } + } + } + + protected void updateOriginalComponentSettings(Composite sourceComposite, + Composite destComposite) { + DataTypeComponent[] sourceComps = sourceComposite.getDefinedComponents(); + DataTypeComponent[] destComps = destComposite.getDefinedComponents(); + assert (sourceComps.length == destComps.length); + for (int i = 0; i < sourceComps.length; i++) { + if (hasSameSettings(sourceComps[i], destComps[i])) { + continue; + } + Settings sourceDtcSettings = sourceComps[i].getDefaultSettings(); + Settings destDtcSettings = destComps[i].getDefaultSettings(); + destDtcSettings.clearAllSettings(); + for (String name : sourceDtcSettings.getNames()) { + destDtcSettings.setValue(name, sourceDtcSettings.getValue(name)); + } + } + } + private boolean hasCompPathNameChanges(Composite currentViewComposite, Composite oldComposite) { // Check component data type pathnames. DataTypeComponent[] comps = currentViewComposite.getDefinedComponents(); @@ -899,20 +967,27 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen @Override public void dataTypeRenamed(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath) { + + DataTypeManager originalDTM = getOriginalDataTypeManager(); + if (dtm != originalDTM) { + return; // Different DTM than the one for this data type. + } + if (!isLoaded()) { return; } + if (oldPath.getDataTypeName().equals(newPath.getDataTypeName())) { return; } - if (originalDataTypePath == null) { - return; - } + String newName = newPath.getDataTypeName(); String oldName = oldPath.getDataTypeName(); // Does the old name match our original name. - if (originalDataTypePath.equals(oldPath)) { + // 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)) { @@ -926,7 +1001,6 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen catch (InvalidNameException e) { Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); } - originalNameChanged(); } else { DataType dt = viewDTM.getDataType(oldPath); @@ -1005,14 +1079,6 @@ public abstract class CompositeEditorModel extends CompositeViewerModel implemen // End of methods for determining if a type of edit action is allowed //================================================================================================== - @Override - protected Composite getOriginalComposite() { - if (!offline) { - return originalComposite; - } - return super.getOriginalComposite(); - } - /** * Saves the current selection in the structure components viewing area. * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java index 70fbfc8437..40acd769c0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorPanel.java @@ -540,7 +540,7 @@ public abstract class CompositeEditorPanel extends JPanel } if (reload) { cancelCellEditing(); // Make sure a field isn't being edited. - model.load(originalDt, model.isOffline()); // reload the structure + model.load(originalDt); // reload the structure model.updateAndCheckChangeState(); } } 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 ffc3896aec..ca55101824 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 @@ -174,11 +174,24 @@ public abstract class CompositeEditorProvider extends ComponentProviderAdapter @Override public ActionContext getActionContext(MouseEvent event) { + + DataTypeComponent componentAt = null; + int[] selectedComponentRows = editorModel.getSelectedComponentRows(); + if (selectedComponentRows.length == 1) { + componentAt = editorModel.getComponent(selectedComponentRows[0]); + } + DataTypeManager originalDTM = editorModel.getOriginalDataTypeManager(); if (originalDTM instanceof ProgramBasedDataTypeManager) { Program program = ((ProgramBasedDataTypeManager) originalDTM).getProgram(); + if (componentAt != null) { + return new ComponentProgramActionContext(this, program, componentAt); + } return new ProgramActionContext(this, program); } + else if (componentAt != null && (originalDTM instanceof StandAloneDataTypeManager)) { + return new ComponentStandAloneActionContext(this, componentAt); + } return new ActionContext(this, null); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerDataTypeManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerDataTypeManager.java index a7ae414a26..0af8174333 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerDataTypeManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeViewerDataTypeManager.java @@ -25,6 +25,7 @@ public class CompositeViewerDataTypeManager extends StandAloneDataTypeManager { */ private DataTypeManager originalDTM; private int transactionID; + /** * Creates a data type manager that the structure editor will use * internally for updating the structure being edited. @@ -50,4 +51,9 @@ public class CompositeViewerDataTypeManager extends StandAloneDataTypeManager { return originalDTM.getType(); } + @Override + public boolean allowsDefaultBuiltInSettings() { + return originalDTM.allowsDefaultBuiltInSettings(); + } + } 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 5a6b46f121..974c79f807 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 @@ -24,7 +24,6 @@ import javax.swing.table.AbstractTableModel; import docking.widgets.fieldpanel.support.FieldRange; import docking.widgets.fieldpanel.support.FieldSelection; -import ghidra.docking.settings.SettingsImpl; import ghidra.program.model.data.*; import ghidra.util.*; import ghidra.util.exception.AssertException; @@ -32,7 +31,8 @@ import ghidra.util.exception.DuplicateNameException; import ghidra.util.task.TaskMonitor; import utility.function.Callback; -class CompositeViewerModel extends AbstractTableModel implements DataTypeManagerChangeListener { +abstract class CompositeViewerModel extends AbstractTableModel + implements DataTypeManagerChangeListener { /** Flag indicating that the model is updating the selection and * should ignore any attempts to set the selection until it is no @@ -41,18 +41,16 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager protected Composite originalComposite; protected DataTypePath originalDataTypePath; - - /** database id for original composite data type. */ - protected long compositeID; + protected long originalCompositeId; protected Composite viewComposite; protected DataTypeManager viewDTM; private List modelListeners = new ArrayList<>(); - /** OriginalCompositeListeners */ - private List originalListeners = new ArrayList<>(); + /** the current status */ private String status = ""; + /** The selection associated with the components. */ protected FieldSelection selection; @@ -148,57 +146,20 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager /** * Updates the model to now view the indicated data structure. + * This method should cleanup from a previous load if neccessary + * and must initilize the following model state: + *

    + *
  • originalComposite
  • + *
  • originalDataTypePath
  • + *
  • originalCompositeId
  • + *
  • originalDataTypePath
  • + *
  • viewComposite
  • + *
  • viewDTM
  • + *
* * @param dataType the composite date type to be viewed. */ - void load(Composite dataType) { - DataTypeManager dataTypeManager = dataType.getDataTypeManager(); - if (dataTypeManager == null) { - throw new IllegalArgumentException( - "Datatype " + dataType.getName() + " doesn't have a data type manager specified."); - } - CategoryPath categoryPath = dataType.getCategoryPath(); - if (categoryPath == null) { - throw new IllegalArgumentException( - "Datatype " + dataType.getName() + " doesn't have a category path specified."); - } - Category category = dataTypeManager.getCategory(categoryPath); - if (category == null) { - throw new IllegalArgumentException( - "Datatype " + dataType.getName() + " doesn't have a category specified."); - } - - if (isLoaded()) { - unload(); - } - - // No longer want to listen for changes to previous category. - DataTypeManager originalDTM = - (originalComposite != null) ? originalComposite.getDataTypeManager() : null; - if (originalDTM != null) { - originalDTM.removeDataTypeManagerListener(this); - } - - // DataType should be a Composite. - originalComposite = dataType; - originalDataTypePath = dataType.getDataTypePath(); - viewComposite = originalComposite; - - // Listen so we can update editor if name changes for this structure. - if (originalDTM != null) { - if (originalDTM.contains(dataType)) { - compositeID = originalDTM.getID(dataType); // Get the id if editing an existing data type. - } - originalDTM.addDataTypeManagerListener(this); - } - setSelection(new FieldSelection()); - clearStatus(); - originalNameChanged(); - originalCategoryChanged(); - compositeInfoChanged(); - fireTableDataChanged(); - componentDataChanged(); - } + abstract void load(Composite dataType); /** * Unloads the currently loaded composite data type. @@ -216,12 +177,9 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager originalDTM.removeDataTypeManagerListener(this); originalDTM = null; } - if (this.originalComposite != null) { - this.originalComposite = null; - } - if (this.viewComposite != null) { - this.viewComposite = null; - } + originalComposite = null; + originalCompositeId = DataTypeManager.NULL_DATATYPE_ID; + viewComposite = null; originalDataTypePath = null; if (viewDTM != null) { viewDTM.close(); @@ -295,7 +253,7 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager */ protected Composite getOriginalComposite() { DataTypeManager originalDTM = getOriginalDataTypeManager(); - if (originalDTM != null) { + if (originalDataTypePath != null && originalDTM != null) { DataType dt = originalDTM.getDataType(originalDataTypePath); if (dt instanceof Composite) { return (Composite) dt; @@ -309,7 +267,7 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager * @return the name */ public final String getOriginalDataTypeName() { - return originalDataTypePath.getDataTypeName(); + return originalDataTypePath != null ? originalDataTypePath.getDataTypeName() : ""; } /** @@ -590,7 +548,7 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager } else if (columnIndex == getMnemonicColumn()) { DataType dt = dtc.getDataType(); - value = dt.getMnemonic(new SettingsImpl()); + value = dt.getMnemonic(dtc.getDefaultSettings()); int compLen = dtc.getLength(); int dtLen = dt.isZeroLength() ? 0 : dt.getLength(); if (dtLen > compLen) { @@ -700,22 +658,6 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager modelListeners.remove(listener); } - /** - * Adds an OriginalCompositeListener to be notified when changes occur - * @param listener the listener - */ - public void addOriginalCompositeListener(OriginalCompositeListener listener) { - originalListeners.add(listener); - } - - /** - * Removes an OriginalCompositeListener that was being notified when changes occur - * @param listener the listener - */ - public void removeOriginalCompositeListener(OriginalCompositeListener listener) { - originalListeners.remove(listener); - } - /** * Called whenever the composite's non-component information changes. * For example, the name, or description change. @@ -731,37 +673,6 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager notify(modelListeners, CompositeViewerModelListener::componentDataChanged); } - /** - * Called when the original composite we are editing has been changed. - */ - public void originalNameChanged() { - - notify(originalListeners, listener -> { - String originalDtName = getOriginalDataTypeName(); - listener.originalNameChanged(originalDtName); - provider.updateTitle(); - }); - } - - /** - * Called when the original composite we are editing has been changed. - */ - public void originalCategoryChanged() { - - notify(originalListeners, listener -> { - CategoryPath originalCatPath = getOriginalCategoryPath(); - listener.originalCategoryChanged(originalCatPath); - }); - } - - /** - * Called when the original composite we are editing has been changed. - */ - public void originalComponentsChanged() { - - notify(originalListeners, OriginalCompositeListener::originalComponentsChanged); - } - /** * Determines the full path name for the composite data type based on * the original composite and original category. @@ -776,8 +687,23 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager // new categories don't matter } + @Override + public void sourceArchiveAdded(DataTypeManager dataTypeManager, SourceArchive dataTypeSource) { + // don't care + } + + @Override + public void sourceArchiveChanged(DataTypeManager dataTypeManager, + SourceArchive dataTypeSource) { + // don't care + } + @Override public void categoryRemoved(DataTypeManager dtm, CategoryPath path) { + DataTypeManager originalDTM = getOriginalDataTypeManager(); + if (dtm != originalDTM) { + return; // Different DTM than the one for this data type. + } if (originalDataTypePath.isAncestor(path)) { String msg = "\"" + originalDataTypePath.getDataTypeName() + "\" had its category \"" + path.getPath() + "\" removed."; @@ -796,12 +722,14 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager String suffix = originalCategory.substring(oldPath.getPath().length()); originalDataTypePath = new DataTypePath(newPath + suffix, originalDataTypePath.getDataTypeName()); - originalCategoryChanged(); } @Override public void categoryRenamed(DataTypeManager dtm, CategoryPath oldPath, CategoryPath newPath) { - + DataTypeManager originalDTM = getOriginalDataTypeManager(); + if (dtm != originalDTM) { + return; // Different DTM than the one for this data type. + } if (!viewDTM.containsCategory(oldPath)) { return; } @@ -823,6 +751,10 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager @Override public void categoryMoved(DataTypeManager dtm, CategoryPath oldPath, CategoryPath newPath) { + DataTypeManager originalDTM = getOriginalDataTypeManager(); + if (dtm != originalDTM) { + return; // Different DTM than the one for this data type. + } if (!viewDTM.containsCategory(oldPath)) { return; } @@ -852,6 +784,12 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager @Override public void dataTypeRemoved(DataTypeManager dtm, DataTypePath path) { + + DataTypeManager originalDTM = getOriginalDataTypeManager(); + if (dtm != originalDTM) { + return; // Different DTM than the one for this data type. + } + DataType dataType = viewDTM.getDataType(path.getCategoryPath(), path.getDataTypeName()); if (dataType == null) { return; @@ -863,9 +801,7 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager DataType dt = viewDTM.getDataType(dtPath); if (dt != null) { if (hasSubDt(viewComposite, dtPath)) { - String msg = - "Removed data type \"" + dtPath + "\", which is a sub-component of \"" + - originalDataTypePath.getDataTypeName() + "\"."; + String msg = "Removed sub-component data type \"" + dtPath; setStatus(msg, true); } viewDTM.remove(dt, TaskMonitor.DUMMY); @@ -885,62 +821,39 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager @Override public void dataTypeRenamed(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath) { - String oldName = oldPath.getDataTypeName(); - String newName = newPath.getDataTypeName(); - if (oldName.equals(newName)) { + + DataTypeManager originalDTM = getOriginalDataTypeManager(); + if (dtm != originalDTM) { + return; // Different DTM than the one for this data type. + } + + DataType dt = viewDTM.getDataType(oldPath); + if (dt == null) { return; } - // Does the old name match our original name. - if (originalDataTypePath.equals(oldPath)) { - originalDataTypePath = newPath; - if (!newName.equals(originalComposite.getName())) { - try { - originalComposite.setName(newName); // Is this the correct thing to do? - } - catch (DuplicateNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - catch (InvalidNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - } - try { - if (viewComposite.getName().equals(oldName)) { - viewComposite.setName(newName); - compositeInfoChanged(); - } - } - catch (DuplicateNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - catch (InvalidNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - originalNameChanged(); + try { + dt.setName(newPath.getDataTypeName()); + fireTableDataChanged(); + componentDataChanged(); } - else { - DataType dt = viewDTM.getDataType(oldPath); - if (dt != null) { - try { - dt.setName(newName); - fireTableDataChanged(); - componentDataChanged(); - } - catch (InvalidNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - catch (DuplicateNameException e) { - Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); - } - } + catch (InvalidNameException e) { + Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); + } + catch (DuplicateNameException e) { + Msg.error(this, "Unexpected Exception: " + e.getMessage(), e); } } @Override public void dataTypeMoved(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath) { - DataType dt = viewDTM.getDataType(oldPath); + DataTypeManager originalDTM = getOriginalDataTypeManager(); + if (dtm != originalDTM) { + return; // Different DTM than the one for this data type. + } + + DataType dt = viewDTM.getDataType(oldPath); if (dt == null) { return; } @@ -956,7 +869,6 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager if (originalDataTypePath.getDataTypeName().equals(newPath.getDataTypeName()) && originalDataTypePath.getCategoryPath().equals(oldPath.getCategoryPath())) { originalDataTypePath = newPath; - originalCategoryChanged(); compositeInfoChanged(); } else { @@ -967,13 +879,15 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager @Override public void dataTypeChanged(DataTypeManager dtm, DataTypePath path) { - if (isLoaded()) { - // If we don't currently have any modifications that need applying and - // the structure in the editor just changed, then show the changed - // structure. - if (originalDataTypePath.equals(path)) { - originalComponentsChanged(); + DataTypeManager originalDTM = getOriginalDataTypeManager(); + if (dtm != originalDTM) { + return; // Different DTM than the one for this data type. + } + + if (isLoaded()) { + if (originalCompositeId != DataTypeManager.NULL_DATATYPE_ID && + path.equals(originalDataTypePath)) { compositeInfoChanged(); } else { @@ -983,8 +897,8 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager if (dt == null) { return; } - DataTypeManager originalDTM = getOriginalDataTypeManager(); if (originalDTM != viewDTM) { + // update changed datatype DataType dataType = dtm.getDataType(path); viewDTM.resolve(dataType, DataTypeConflictHandler.REPLACE_HANDLER); } @@ -997,45 +911,43 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager @Override public void dataTypeReplaced(DataTypeManager dtm, DataTypePath oldPath, DataTypePath newPath, DataType newDataType) { - if (newDataType.getDataTypeManager() == null) { - throw new IllegalArgumentException("Datatype " + newDataType.getName() + - " doesn't have a data type manager specified."); - } + DataTypeManager originalDTM = getOriginalDataTypeManager(); if (dtm != originalDTM) { return; // Different DTM than the one for this data type. } - if (isLoaded()) { - if (!oldPath.equals(originalDataTypePath)) { // am I editing the replaced dataType? - DataType dt = viewDTM.getDataType(oldPath); - if (dt != null) { - if (hasSubDt(viewComposite, oldPath)) { - String msg = "Replaced data type \"" + oldPath.getPath() + - "\", which is a sub-component of \"" + - originalDataTypePath.getDataTypeName() + "\"."; - setStatus(msg, true); - } - try { - viewDTM.replaceDataType(dt, newDataType, true); - } - catch (DataTypeDependencyException e) { - throw new AssertException(e); - } - fireTableDataChanged(); - componentDataChanged(); + if (!isLoaded()) { + return; + } + + if (!oldPath.equals(originalDataTypePath)) { // am I editing the replaced dataType? + DataType dt = viewDTM.getDataType(oldPath); + if (dt != null) { + if (hasSubDt(viewComposite, oldPath)) { + String msg = "Replaced sub-component data type \"" + oldPath.getPath(); + setStatus(msg, true); } + try { + + viewDTM.replaceDataType(dt, newDataType, true); + } + catch (DataTypeDependencyException e) { + throw new AssertException(e); + } + fireTableDataChanged(); + componentDataChanged(); } - else { - load((Composite) newDataType); - } + } + else { + load((Composite) newDataType); } } @Override public void favoritesChanged(DataTypeManager dtm, DataTypePath path, boolean isFavorite) { // Don't care. - } + } /******************************************************************* * Helper methods for CategoryChangeListener methods. @@ -1123,14 +1035,13 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager * @return the selected row count */ public int getNumSelectedRows() { - int numRanges = this.selection.getNumRanges(); - int numSelected = 0; - for (int i = 0; i < numRanges; i++) { - FieldRange range = this.selection.getFieldRange(i); - numSelected += - range.getEnd().getIndex().intValue() - range.getStart().getIndex().intValue(); + int count = 0; + for (FieldRange range : this.selection) { + int endIndex = range.getEnd().getIndex().intValue(); + int startIndex = range.getStart().getIndex().intValue(); + count += endIndex - startIndex; } - return numSelected; + return count; } /** @@ -1140,17 +1051,17 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager * @return the selected row count */ public int getNumSelectedComponentRows() { - FieldSelection tmpSelection = new FieldSelection(); - tmpSelection.addRange(0, getNumComponents()); - tmpSelection.intersect(this.selection); - int numRanges = tmpSelection.getNumRanges(); - int numSelected = 0; - for (int i = 0; i < numRanges; i++) { - FieldRange range = tmpSelection.getFieldRange(i); - numSelected += - range.getEnd().getIndex().intValue() - range.getStart().getIndex().intValue(); + int numComponents = getNumComponents(); + int count = 0; + for (FieldRange range : this.selection) { + int endIndex = Math.min(range.getEnd().getIndex().intValue(), numComponents); + int startIndex = range.getStart().getIndex().intValue(); + int rangeCount = endIndex - startIndex; + if (rangeCount > 0) { + count += rangeCount; + } } - return numSelected; + return count; } /** @@ -1200,19 +1111,15 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager * @return the selected rows */ public int[] getSelectedRows() { - int[] selectedRows = new int[getNumSelectedRows()]; - int newIndex = 0; - int numRanges = selection.getNumRanges(); - for (int i = 0; i < numRanges; i++) { - FieldRange range = selection.getFieldRange(i); - for (int tempRow = - range.getStart().getIndex().intValue(); tempRow < range.getEnd() - .getIndex() - .intValue(); tempRow++) { - selectedRows[newIndex++] = tempRow; + ArrayList list = new ArrayList<>(); + for (FieldRange range : this.selection) { + int endIndex = range.getEnd().getIndex().intValue(); + int startIndex = range.getStart().getIndex().intValue(); + for (int i = startIndex; i < endIndex; i++) { + list.add(i); } } - return selectedRows; + return list.stream().mapToInt(Integer::intValue).toArray(); } /** @@ -1220,21 +1127,16 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager * @return the selected rows */ public int[] getSelectedComponentRows() { - int[] selectedRows = new int[getNumSelectedComponentRows()]; - int newIndex = 0; + ArrayList list = new ArrayList<>(); int numComponents = getNumComponents(); - int numRanges = selection.getNumRanges(); - for (int i = 0; i < numRanges; i++) { - FieldRange range = selection.getFieldRange(i); - for (int tempRow = - range.getStart().getIndex().intValue(); tempRow < range.getEnd() - .getIndex() - .intValue() && - tempRow < numComponents; tempRow++) { - selectedRows[newIndex++] = tempRow; + for (FieldRange range : this.selection) { + int endIndex = Math.min(range.getEnd().getIndex().intValue(), numComponents); + int startIndex = range.getStart().getIndex().intValue(); + for (int i = startIndex; i < endIndex; i++) { + list.add(i); } } - return selectedRows; + return list.stream().mapToInt(Integer::intValue).toArray(); } /** @@ -1428,17 +1330,7 @@ class CompositeViewerModel extends AbstractTableModel implements DataTypeManager } protected long getCompositeID() { - return compositeID; + return originalCompositeId; } - @Override - public void sourceArchiveAdded(DataTypeManager dataTypeManager, SourceArchive dataTypeSource) { - // don't care - } - - @Override - public void sourceArchiveChanged(DataTypeManager dataTypeManager, - SourceArchive dataTypeSource) { - // don't care - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorModel.java index 3c0c154eb3..0a73bfea39 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorModel.java @@ -23,15 +23,8 @@ import ghidra.util.task.TaskMonitor; public interface EditorModel { - /** - * Loads the specified composite into the model replacing - * whatever composite is there. - * - * @param dataType the new composite data type. - * @param offline false indicates don't try to keep the composite itself - * in the editor's data type manager. - */ - public void load(Composite dataType, boolean offline); + // TODO: This model interface serves no real purpose and could be collapsed into the + // abstract class CompositeEditorModel implementation. /** * Called when the model is no longer needed. @@ -397,23 +390,6 @@ public interface EditorModel { */ public boolean moveDown() throws UsrException; -// /** -// * -// * @param dataType -// * @return -// * @throws UsrException -// */ -// public DataTypeComponent replace(DataType dataType) throws UsrException; -// -// /** -// * -// * @param rowIndex -// * @param dataType -// * @return -// * @throws UsrException -// */ -// public DataTypeComponent replace(int rowIndex, DataType dataType) throws UsrException; - /** * * @param rowIndex diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorModel.java index 6244ebd3a8..361fc2288f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorModel.java @@ -24,7 +24,6 @@ import docking.widgets.dialogs.InputDialog; import docking.widgets.dialogs.InputDialogListener; import docking.widgets.fieldpanel.support.FieldRange; import docking.widgets.fieldpanel.support.FieldSelection; -import ghidra.docking.settings.SettingsImpl; import ghidra.program.model.data.*; import ghidra.program.model.lang.InsufficientBytesException; import ghidra.util.Msg; @@ -80,11 +79,6 @@ class StructureEditorModel extends CompEditorModel { return COMMENT; } - @Override - public void load(Composite dataType, boolean useOffLineCategory) { - super.load(dataType, useOffLineCategory); - } - @Override public void load(Composite dataType) { super.load(dataType); @@ -143,7 +137,7 @@ class StructureEditorModel extends CompEditorModel { } else if (columnIndex == getMnemonicColumn()) { DataType dt = dtc.getDataType(); - value = dt.getMnemonic(new SettingsImpl()); + value = dt.getMnemonic(dtc.getDefaultSettings()); int compLen = dtc.getLength(); int dtLen = dt.isZeroLength() ? 0 : dt.getLength(); if (dtLen > compLen) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/AbstractSettingsDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/AbstractSettingsDialog.java new file mode 100644 index 0000000000..aadd6ecd3e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/AbstractSettingsDialog.java @@ -0,0 +1,750 @@ +/* ### + * 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.data; + +import java.awt.*; +import java.awt.event.MouseEvent; +import java.math.BigInteger; +import java.util.*; +import java.util.List; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.table.TableCellEditor; + +import docking.DialogComponentProvider; +import docking.widgets.combobox.GComboBox; +import docking.widgets.dialogs.StringChoices; +import docking.widgets.table.*; +import docking.widgets.textfield.IntegerTextField; +import ghidra.docking.settings.*; +import ghidra.framework.preferences.Preferences; +import ghidra.util.BigEndianDataConverter; +import ghidra.util.HTMLUtilities; +import ghidra.util.exception.AssertException; +import ghidra.util.exception.CancelledException; +import ghidra.util.table.GhidraTable; + +public abstract class AbstractSettingsDialog extends DialogComponentProvider { + + private final static int WIDTH = 400; + private final static int HEIGHT = 150; + + private static String[] BOOLEAN_CHOICES = { "yes", "no" }; + private static String NO_CHOICE = ""; + + private SettingsDefinition[] settingsDefinitions; + private Settings defaultSettings; // may be null + private SettingsImpl settings; // holder for setting edits + + private SettingsTableModel settingsTableModel; + private SettingsTable settingsTable; + + private Map intHexModeMap; // used to track/cache integer hex mode preference per setting + private boolean appliedSettings; + + /** + * Construct a Settings dialog. If original settings are null, all initial settings + * values will be blank and no default specified. + * @param title dialog title + * @param settingDefinitions settings definitions to be displayed + * @param originalSettings original settings to be modified may (may be null) + */ + protected AbstractSettingsDialog(String title, SettingsDefinition[] settingDefinitions, + Settings originalSettings) { + super(title, true, false, true, false); + this.settingsDefinitions = settingDefinitions; + settings = new SettingsImpl(originalSettings); + defaultSettings = settings.getDefaultSettings(); + if (originalSettings != null && defaultSettings == null) { + // ensure we have defaults to facilitate revert to default + defaultSettings = new SettingsImpl(); + settings.setDefaultSettings(defaultSettings); + } + buildPanel(); + } + + /** + * Get settings definitions specified with dialog construction + * @return settings definitions + */ + protected SettingsDefinition[] getSettingsDefinitions() { + return settingsDefinitions; + } + + /** + * Get settings which contain modifications which may be applied to + * original settings. + * @return settings + */ + protected Settings getSettings() { + return settings; + } + + /** + * Get default setting specified with dialog construction + * @return settings or null if not specified + */ + protected Settings getDefaultSettings() { + return defaultSettings; + } + + GTable getSettingsTable() { + return settingsTable; + } + + SettingsTableModel getSettingsTableModel() { + return settingsTableModel; + } + + @Override + public void dispose() { + close(); + settingsDefinitions = null; + defaultSettings = null; + settings = null; + } + + boolean hasSettings() { + return settingsDefinitions.length != 0; + } + + private void buildPanel() { + addWorkPanel(buildWorkPanel()); + addButtons(); + } + + private void addButtons() { + + addOKButton(); + + JButton newApplyButton = new JButton("Apply"); + newApplyButton.addActionListener(e -> apply()); + addButton(newApplyButton); + + addCancelButton(); + } + + private String getHexModePropertyName(SettingsDefinition settingsDef) { + return settingsDef.getClass().getSimpleName() + ".hexMode"; + } + + private void readHexModePreferences() { + intHexModeMap = new HashMap<>(); + for (int i = 0; i < settingsDefinitions.length; i++) { + if (settingsDefinitions[i] instanceof NumberSettingsDefinition) { + String propertyName = getHexModePropertyName(settingsDefinitions[i]); + boolean hexMode = Boolean + .valueOf( + Preferences.getProperty(propertyName, Boolean.FALSE.toString())); + intHexModeMap.put(settingsDefinitions[i].getName(), hexMode); + } + } + } + + private void writeHexModePreferences() { + boolean save = false; + for (int i = 0; i < settingsDefinitions.length; i++) { + if (settingsDefinitions[i] instanceof NumberSettingsDefinition) { + boolean hexMode = intHexModeMap.get(settingsDefinitions[i].getName()); + String propertyName = getHexModePropertyName(settingsDefinitions[i]); + if (hexMode != Boolean + .valueOf(Preferences.getProperty(propertyName, Boolean.FALSE.toString()))) { + Preferences.setProperty(propertyName, Boolean.toString(hexMode)); + save = true; + } + } + } + if (save) { + Preferences.store(); + } + } + + private boolean isHexModeEnabled(SettingsDefinition settingsDef) { + return intHexModeMap.get(settingsDef.getName()); + } + + private JPanel buildWorkPanel() { + JPanel workPanel = new JPanel(new BorderLayout()); + workPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + + readHexModePreferences(); + + settingsTableModel = new SettingsTableModel(settingsDefinitions); + settingsTableModel.addTableModelListener(e -> appliedSettings = false); + settingsTable = new SettingsTable(settingsTableModel); + settingsTable.setAutoscrolls(true); + settingsTable.setRowSelectionAllowed(false); + settingsTable.setColumnSelectionAllowed(false); + + // make the rows a bit taller to allow the integer text field editor to render correctly + settingsTable.setRowHeight(22); + + // disable user sorting and column adding (we don't expect enough data to require sorting) + settingsTable.getTableHeader().setReorderingAllowed(false); + settingsTable.setColumnHeaderPopupEnabled(false); + settingsTable.setUserSortingEnabled(false); + + settingsTable.setDefaultRenderer(Settings.class, new SettingsRenderer()); + settingsTable.setDefaultEditor(Settings.class, new SettingsEditor()); + + JScrollPane scrollpane = new JScrollPane(settingsTable); + scrollpane.setPreferredSize(new Dimension(WIDTH, HEIGHT)); + + workPanel.add(scrollpane, BorderLayout.CENTER); + + return workPanel; + } + + @Override + protected void cancelCallback() { + close(); + dispose(); + } + + @Override + protected void okCallback() { + apply(); + close(); + dispose(); + } + + private void apply() { + try { + applySettings(); + } + catch (CancelledException e) { + return; + } + writeHexModePreferences(); + appliedSettings = true; + } + + /** + * Apply changes to settings. This method must be ov + * @throws CancelledException thrown if apply operation cancelled + */ + protected abstract void applySettings() throws CancelledException; + + protected boolean isSettingsApplied() { + return appliedSettings; + } + + protected StringChoices getChoices(EnumSettingsDefinition def) { + String[] choices = def.getDisplayChoices(settings); + int currentChoice = def.getChoice(settings); + if (defaultSettings == null) { + choices = addNoChoice(choices); + if (!def.hasValue(settings)) { + currentChoice = 0; + } + else { + ++currentChoice; // account for presence of No-Choice + } + } + StringChoices choicesEnum = new StringChoices(choices); + choicesEnum.setSelectedValue(currentChoice); + return choicesEnum; + } + + protected StringChoices getChoices(BooleanSettingsDefinition def) { + String[] choices = BOOLEAN_CHOICES; + int currentChoice = def.getValue(settings) ? 0 : 1; + if (defaultSettings == null) { + choices = addNoChoice(choices); + if (!def.hasValue(settings)) { + currentChoice = 0; + } + else { + ++currentChoice; // account for presence of No-Choice + } + } + StringChoices choicesEnum = new StringChoices(choices); + choicesEnum.setSelectedValue(currentChoice); + return choicesEnum; + } + + protected void setChoice(Object value, EnumSettingsDefinition def) { + StringChoices choices = (StringChoices) value; + int selectedChoice = choices.getSelectedValueIndex(); + if (defaultSettings == null) { + if (selectedChoice == 0) { // blank choosen + settings.clearSetting(def.getName()); + return; + } + --selectedChoice; // account for presence of No-Choice + } + def.setChoice(settings, selectedChoice); + } + + protected void setChoice(Object value, BooleanSettingsDefinition def) { + StringChoices choices = (StringChoices) value; + int selectedChoice = choices.getSelectedValueIndex(); + if (defaultSettings == null) { + if (selectedChoice == 0) { // blank choosen + settings.clearSetting(def.getName()); + return; + } + --selectedChoice; // account for presence of No-Choice + } + def.setValue(settings, selectedChoice == 0); + } + + protected void setValue(Number value, NumberSettingsDefinition def) { + if (value == null) { + def.clear(settings); + } + else { + def.setValue(settings, value.longValue()); + } + } + + protected void setValue(String value, StringSettingsDefinition def) { + if (value == null) { + def.clear(settings); + } + else { + def.setValue(settings, value); + } + } + + private String[] addNoChoice(String[] choices) { + String[] newChoices = new String[choices.length + 1]; + newChoices[0] = NO_CHOICE; + System.arraycopy(choices, 0, newChoices, 1, choices.length); + return newChoices; + } + +//================================================================================================== +// Inner Classes +//================================================================================================== + + class SettingsTable extends GhidraTable { + + public SettingsTable(SettingsTableModel settingsTableModel) { + super(settingsTableModel); + } + + @Override + public String getToolTipText(MouseEvent evt) { + int col = this.columnAtPoint(evt.getPoint()); + if (col != 0) { + return super.getToolTipText(); + } + int row = this.rowAtPoint(evt.getPoint()); + SettingsRowObject rowObject = settingsTableModel.getRowObject(row); + String description = rowObject.definition.getDescription(); + if (!description.isEmpty()) { + return "" + HTMLUtilities.escapeHTML(description) + ""; + } + return null; + } + } + + class SettingsRowObject { + + private SettingsDefinition definition; + + SettingsRowObject(SettingsDefinition definition) { + this.definition = definition; + } + + public String getName() { + return definition.getName(); + } + + Object getSettingsObject() { + if (definition instanceof EnumSettingsDefinition) { + StringChoices choices = getChoices((EnumSettingsDefinition) definition); + return choices; + } + else if (definition instanceof BooleanSettingsDefinition) { + StringChoices choices = getChoices((BooleanSettingsDefinition) definition); + return choices; + } + else if (definition instanceof NumberSettingsDefinition) { + NumberSettingsDefinition def = (NumberSettingsDefinition) definition; + if (defaultSettings == null && !def.hasValue(settings)) { + return new NumberWrapper(null); // show blank value + } + return new NumberWrapper(def.getValue(settings)); + } + else if (definition instanceof StringSettingsDefinition) { + StringSettingsDefinition def = (StringSettingsDefinition) definition; + if (defaultSettings == null && !def.hasValue(settings)) { + return new StringWrapper(null); // show blank value + } + return new StringWrapper(def.getValue(settings)); + } + return ""; + } + + boolean useDefault() { + if (definition instanceof EnumSettingsDefinition) { + EnumSettingsDefinition def = (EnumSettingsDefinition) definition; + return def.getChoice(settings) == def.getChoice(defaultSettings); + } + else if (definition instanceof BooleanSettingsDefinition) { + BooleanSettingsDefinition def = (BooleanSettingsDefinition) definition; + return def.getValue(settings) == def.getValue(defaultSettings); + } + else if (definition instanceof NumberSettingsDefinition) { + NumberSettingsDefinition def = (NumberSettingsDefinition) definition; + return def.getValue(settings) == def.getValue(defaultSettings); + } + else if (definition instanceof StringSettingsDefinition) { + StringSettingsDefinition def = (StringSettingsDefinition) definition; + return def.getValue(settings) == def.getValue(defaultSettings); + } + return false; + } + + boolean setSettingsChoice(Object value) { + if (definition instanceof EnumSettingsDefinition) { + setChoice(value, (EnumSettingsDefinition) definition); + return true; + } + else if (definition instanceof BooleanSettingsDefinition) { + setChoice(value, (BooleanSettingsDefinition) definition); + return true; + } + else if (definition instanceof NumberSettingsDefinition) { + setValue((Number) value, (NumberSettingsDefinition) definition); + return true; + } + else if (definition instanceof StringSettingsDefinition) { + setValue((String) value, (StringSettingsDefinition) definition); + return true; + } + return false; + } + + void clear(SettingsImpl s) { + definition.clear(s); + } + } + + private class SettingsTableModel extends AbstractSortedTableModel { + + private List rows = new ArrayList<>(); + + SettingsTableModel(SettingsDefinition[] settingsDefs) { + for (SettingsDefinition sd : settingsDefs) { + rows.add(new SettingsRowObject(sd)); + } + } + + @Override + public List getModelData() { + return rows; + } + + @Override + public String getName() { + return "Settings Definition Model"; + } + + @Override + public boolean isSortable(int columnIndex) { + return columnIndex == 0; + } + + @Override + public boolean isCellEditable(int row, int col) { + return col != 0; + } + + @Override + public int getColumnCount() { + return defaultSettings != null ? 3 : 2; + } + + @Override + public String getColumnName(int col) { + switch (col) { + case 0: + return "Name"; + case 1: + return "Settings"; + case 2: + return "Use Default"; + } + return null; + } + + // override this to force the correct cell editors to be used + @Override + public Class getColumnClass(int col) { + switch (col) { + case 0: + return String.class; + case 1: + return Settings.class; + case 2: + return Boolean.class; + } + return null; + } + + @Override + public Object getColumnValueForRow(SettingsRowObject t, int columnIndex) { + switch (columnIndex) { + case 0: + return t.getName(); + case 1: + return t.getSettingsObject(); + case 2: + return t.useDefault(); + } + return null; + } + + @Override + public void setValueAt(Object value, int row, int col) { + SettingsRowObject rowObject = rows.get(row); + switch (col) { + case 1: + if (rowObject.setSettingsChoice(value)) { + fireTableDataChanged(); + } + break; + case 2: + if (((Boolean) value).booleanValue()) { + rowObject.clear(settings); + fireTableDataChanged(); + } + break; + } + } + } + + private String getIntegerString(Number num, NumberSettingsDefinition settingsDef) { + long value = num.longValue(); + boolean decimalMode = !settingsDef.isHexModePreferred() && !isHexModeEnabled(settingsDef); + if (!settingsDef.allowNegativeValue()) { + byte[] bytes = BigEndianDataConverter.INSTANCE.getBytes(value); + BigInteger unsignedValue = new BigInteger(1, bytes); + if (decimalMode) { + return unsignedValue.toString(); + } + return "0x" + unsignedValue.toString(16); + } + if (decimalMode) { + return Long.toString(value); // signed decimal + } + BigInteger signedValue = BigInteger.valueOf(value); + String sign = ""; + if (signedValue.signum() < 0) { + sign = "-"; + signedValue = signedValue.negate(); + } + return sign + "0x" + signedValue.toString(16); + } + + + private class SettingsRenderer extends GTableCellRenderer { + + private Font originalFont; + + @Override + public Component getTableCellRendererComponent(GTableCellRenderingData tableData) { + JLabel renderer = (JLabel) super.getTableCellRendererComponent(tableData); + renderer.setAlignmentX(Component.LEFT_ALIGNMENT); + if (originalFont != null) { + renderer.setFont(originalFont); + } + else { + originalFont = renderer.getFont(); + } + + Object value = tableData.getValue(); + if (value instanceof NumberWrapper) { + Number n = ((NumberWrapper) value).value; + if (n != null) { + // A Renderer that will show number values the same as the integer text field + // based upon retained hex mode preference + SettingsRowObject rowObject = (SettingsRowObject) tableData.getRowObject(); + String valString = + getIntegerString(n, (NumberSettingsDefinition) rowObject.definition); + renderer.setText(valString); + } + } + else if (value instanceof StringWrapper) { + String str = ((StringWrapper) value).value; + if (str == null) { + renderer.setText("--default--"); + renderer.setFont(originalFont.deriveFont(Font.ITALIC)); + } + } + return renderer; + } + } + + private class NumberWrapper { + + final Number value; // may be null + + NumberWrapper(Number value) { + this.value = value; + } + + @Override + public String toString() { + return value == null ? "" : Long.toString(value.longValue()); + } + } + + private class StringWrapper { + + final String value; // may be null + + StringWrapper(String value) { + this.value = value; + } + + @Override + public String toString() { + return value == null ? "" : value; + } + } + + class SettingsEditor extends AbstractCellEditor implements TableCellEditor { + + final static int ENUM = 0; + final static int BOOLEAN = 1; + final static int NUMBER = 2; + final static int STRING = 3; + + private int mode; + private GComboBox comboBox = new GComboBox<>(); + private IntegerTextField intTextField = new IntegerTextField(); + private JTextField textField = new JTextField(); + + private SettingsRowObject rowobject; + + SettingsEditor() { + comboBox.addItemListener(e -> fireEditingStopped()); + intTextField.addChangeListener(e -> updateHexMode()); + } + + GComboBox getComboBox() { + return comboBox; // used for testing + } + + @Override + public Object getCellEditorValue() { + switch (mode) { + case ENUM: + return getComboBoxEnum(); + case BOOLEAN: + return getComboBoxEnum(); + case NUMBER: + return getNumber(); + case STRING: + return getString(); + } + throw new AssertException(); + } + + private StringChoices getComboBoxEnum() { + String[] items = new String[comboBox.getItemCount()]; + for (int i = 0; i < items.length; i++) { + items[i] = comboBox.getItemAt(i); + } + StringChoices enuum = new StringChoices(items); + enuum.setSelectedValue(comboBox.getSelectedIndex()); + return enuum; + } + + private void updateHexMode() { + intHexModeMap.put(rowobject.definition.getName(), intTextField.isHexMode()); + } + + private Number getNumber() { + BigInteger currentValue = intTextField.getValue(); + if (currentValue == null) { + return null; + } + return currentValue.longValue(); + } + + private String getString() { + String value = textField.getText().trim(); + return value.length() == 0 ? null : value; + } + + @Override + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, + int row, int column) { + rowobject = settingsTableModel.getRowObject(row); + if (value instanceof StringChoices || value instanceof Boolean) { + initComboBox((StringChoices) value); + return comboBox; + } + if (value instanceof NumberWrapper) { + initIntField(((NumberWrapper) value).value); + return intTextField.getComponent(); + } + if (value instanceof StringWrapper) { + initTextField(((StringWrapper) value).value); + return textField; + } + throw new AssertException( + "SettingsEditor: " + value.getClass().getName() + " not supported"); + } + + private void initComboBox(StringChoices enuum) { + mode = ENUM; + comboBox.removeAllItems(); + String[] items = enuum.getValues(); + for (String item : items) { + comboBox.addItem(item); + } + comboBox.setSelectedIndex(enuum.getSelectedValueIndex()); + } + + private void initIntField(Number value) { + mode = NUMBER; + NumberSettingsDefinition def = (NumberSettingsDefinition) rowobject.definition; + if (def.isHexModePreferred() || isHexModeEnabled(def)) { + intTextField.setHexMode(); + } + else { + intTextField.setDecimalMode(); + } + + intTextField.setMaxValue(def.getMaxValue()); + intTextField.setAllowNegativeValues(def.allowNegativeValue()); + + if (value == null) { + intTextField.setValue(null); + } + else if (def.allowNegativeValue()) { + intTextField.setValue(value.longValue()); + } + else { + byte[] bytes = BigEndianDataConverter.INSTANCE.getBytes(value.longValue()); + intTextField.setValue(new BigInteger(1, bytes)); + } + } + + private void initTextField(String str) { + mode = STRING; + textField.setText(str); + } + + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/ChooseDataTypeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/ChooseDataTypeAction.java index b453fe30a2..2e858aee79 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/ChooseDataTypeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/ChooseDataTypeAction.java @@ -19,9 +19,10 @@ import java.awt.event.KeyEvent; import javax.swing.KeyStroke; -import docking.ActionContext; -import docking.action.*; +import docking.action.KeyBindingData; +import docking.action.KeyBindingType; import ghidra.app.context.ListingActionContext; +import ghidra.app.context.ListingContextAction; import ghidra.app.util.datatype.DataTypeSelectionDialog; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.data.DataType; @@ -31,7 +32,7 @@ import ghidra.util.data.DataTypeParser.AllowedDataTypes; /** * An action that allows the user to change or select a data type. */ -public class ChooseDataTypeAction extends DockingAction { +public class ChooseDataTypeAction extends ListingContextAction { private DataPlugin plugin; private static final KeyStroke KEY_BINDING = KeyStroke.getKeyStroke(KeyEvent.VK_T, 0); @@ -53,11 +54,10 @@ public class ChooseDataTypeAction extends DockingAction { } @Override - public void actionPerformed(ActionContext context) { - ListingActionContext listingContext = (ListingActionContext) context.getContextObject(); - DataType dataType = getDataType(listingContext); + protected void actionPerformed(ListingActionContext context) { + DataType dataType = getDataType(context); if (dataType != null) { - plugin.createData(dataType, listingContext, false, true); + plugin.createData(dataType, context, false, true); } } @@ -74,11 +74,7 @@ public class ChooseDataTypeAction extends DockingAction { } @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - if (contextObject instanceof ListingActionContext) { - return plugin.isCreateDataAllowed(((ListingActionContext) contextObject)); - } - return false; + protected boolean isEnabledForContext(ListingActionContext context) { + return plugin.isCreateDataAllowed(context); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CreateArrayAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CreateArrayAction.java index 8298e2ad82..a9af994281 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CreateArrayAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CreateArrayAction.java @@ -19,13 +19,13 @@ import java.awt.event.KeyEvent; import javax.swing.KeyStroke; -import docking.ActionContext; import docking.action.*; import docking.widgets.OptionDialog; import docking.widgets.dialogs.NumberInputDialog; import ghidra.app.cmd.data.CreateArrayCmd; import ghidra.app.cmd.data.CreateArrayInStructureCmd; import ghidra.app.context.ListingActionContext; +import ghidra.app.context.ListingContextAction; import ghidra.framework.cmd.Command; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.*; @@ -34,7 +34,7 @@ import ghidra.program.model.listing.*; import ghidra.program.model.mem.MemoryBlock; import ghidra.program.util.*; -class CreateArrayAction extends DockingAction { +class CreateArrayAction extends ListingContextAction { private static final KeyStroke DEFAULT_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_OPEN_BRACKET, 0); @@ -62,12 +62,10 @@ class CreateArrayAction extends DockingAction { } @Override - public void actionPerformed(ActionContext context) { - ListingActionContext programActionContext = - (ListingActionContext) context.getContextObject(); - Program program = programActionContext.getProgram(); - ProgramLocation loc = programActionContext.getLocation(); - ProgramSelection sel = programActionContext.getSelection(); + protected void actionPerformed(ListingActionContext context) { + Program program = context.getProgram(); + ProgramLocation loc = context.getLocation(); + ProgramSelection sel = context.getSelection(); if (sel != null && !sel.isEmpty()) { InteriorSelection interiorSel = sel.getInteriorSelection(); @@ -322,12 +320,8 @@ class CreateArrayAction extends DockingAction { } @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - if (contextObject instanceof ListingActionContext) { - return plugin.isCreateDataAllowed(((ListingActionContext) contextObject)); - } - return false; + protected boolean isEnabledForContext(ListingActionContext context) { + return plugin.isCreateDataAllowed(context); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CycleGroupAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CycleGroupAction.java index dde841404a..c225ab8b4f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CycleGroupAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CycleGroupAction.java @@ -17,10 +17,11 @@ package ghidra.app.plugin.core.data; import javax.swing.KeyStroke; -import docking.ActionContext; -import docking.action.*; +import docking.action.KeyBindingData; +import docking.action.KeyBindingType; import ghidra.app.cmd.data.*; import ghidra.app.context.ListingActionContext; +import ghidra.app.context.ListingContextAction; import ghidra.framework.cmd.BackgroundCommand; import ghidra.program.model.address.Address; import ghidra.program.model.data.CycleGroup; @@ -34,7 +35,7 @@ import ghidra.util.Msg; * CycleGroupAction cycles data through a series of data types * defined by a CycleGroup. */ -public class CycleGroupAction extends DockingAction { +public class CycleGroupAction extends ListingContextAction { private DataPlugin plugin; private CycleGroup cycleGroup; @@ -63,26 +64,13 @@ public class CycleGroupAction extends DockingAction { } @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - if (contextObject instanceof ListingActionContext) { - return plugin.isCreateDataAllowed((ListingActionContext) contextObject); - } - return false; + protected boolean isEnabledForContext(ListingActionContext context) { + return plugin.isCreateDataAllowed(context); } @Override - public void actionPerformed(ActionContext context) { - - if (context != null) { - Object contextObject = context.getContextObject(); - - if (contextObject instanceof ListingActionContext) { - ListingActionContext programContextObject = (ListingActionContext) contextObject; - cycleData(programContextObject); - return; - } - } + protected void actionPerformed(ListingActionContext context) { + cycleData(context); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataPlugin.java index 361994735c..835c92196d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataPlugin.java @@ -17,21 +17,31 @@ package ghidra.app.plugin.core.data; import java.util.*; -import docking.ActionContext; +import javax.swing.tree.TreePath; + import docking.action.DockingAction; import docking.action.MenuData; +import docking.action.builder.ActionBuilder; import docking.widgets.OptionDialog; +import docking.widgets.tree.GTree; import ghidra.app.CorePluginPackage; import ghidra.app.cmd.data.*; import ghidra.app.context.ListingActionContext; import ghidra.app.events.ProgramActivatedPluginEvent; import ghidra.app.plugin.PluginCategoryNames; +import ghidra.app.plugin.core.compositeeditor.*; +import ghidra.app.plugin.core.datamgr.DataTypesActionContext; +import ghidra.app.plugin.core.datamgr.tree.DataTypeNode; +import ghidra.app.plugin.core.datamgr.tree.DataTypeTreeNode; import ghidra.app.services.DataService; import ghidra.app.services.DataTypeManagerService; +import ghidra.docking.settings.SettingsDefinition; import ghidra.framework.cmd.BackgroundCommand; import ghidra.framework.cmd.Command; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.program.database.data.DataTypeManagerDB; +import ghidra.program.database.data.ProgramDataTypeManager; import ghidra.program.model.address.*; import ghidra.program.model.data.*; import ghidra.program.model.listing.*; @@ -76,18 +86,17 @@ public class DataPlugin extends Plugin implements DataService { { DATA_MENU_POPUP_PATH, "Edit Data Type..." }; private static final String[] DATA_SETTINGS_POPUP_PATH = { DATA_MENU_POPUP_PATH, "Settings..." }; - private static final String[] DEFAULT_DATA_SETTINGS_POPUP_PATH = + private static final String[] DEFAULT_SETTINGS_POPUP_PATH = { DATA_MENU_POPUP_PATH, "Default Settings..." }; + private static final String[] DATATYPE_SETTINGS_POPUP_PATH = { "Settings..." }; private static final String[] CHOOSE_DATA_TYPE_POPUP_PATH = { DATA_MENU_POPUP_PATH, "Choose Data Type..." }; private DataTypeManagerService dtmService; - private DockingAction settingsAction; - private DockingAction defaultSettingsAction; private DataAction pointerAction; private DataAction recentlyUsedAction; - private DockingAction editDataTypeAction; // Edit a data type action + private DockingAction editDataTypeAction; private CreateStructureAction createStructureAction; private CreateArrayAction createArrayAction; private RenameDataFieldAction renameDataFieldAction; @@ -143,77 +152,72 @@ public class DataPlugin extends Plugin implements DataService { pointerAction = new PointerDataAction(this); tool.addAction(pointerAction); - settingsAction = new DockingAction("Data Settings", getName()) { - @Override - public void actionPerformed(ActionContext context) { - dataSettingsCallback((ListingActionContext) context.getContextObject()); - } + // Data instance settings action based upon data selection in listing + new ActionBuilder("Data Settings", getName()) + .sharedKeyBinding() + .popupMenuPath(DATA_SETTINGS_POPUP_PATH) + .popupMenuGroup("Settings") + .withContext(ListingActionContext.class) + .enabledWhen(context -> isDataTypeSettingsAllowed(context, false)) + .onAction(context -> dataSettingsCallback(context)) + .buildAndInstall(tool); - @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - if (contextObject instanceof ListingActionContext) { - return isDataTypeSettingsAllowed((ListingActionContext) context, false); - } - return false; - } - }; + // Default settings action based upon data selection in listing + new ActionBuilder("Default Settings", getName()) + .sharedKeyBinding() + .popupMenuPath(DEFAULT_SETTINGS_POPUP_PATH) + .popupMenuGroup("Settings") + .withContext(ListingActionContext.class) + .enabledWhen(context -> isDataTypeSettingsAllowed(context, true)) + .onAction(context -> editDefaultDataSettings(context)) + .buildAndInstall(tool); - settingsAction.setPopupMenuData(new MenuData(DATA_SETTINGS_POPUP_PATH, null, "Settings")); + // Default settings action for selected datatypes from datatype manager + new ActionBuilder("Default Settings", getName()) + .sharedKeyBinding() + .popupMenuPath(DATATYPE_SETTINGS_POPUP_PATH) + .popupMenuGroup("Settings") + .withContext(DataTypesActionContext.class) + .enabledWhen(context -> isDefaultDataTypeSettingsAllowed(context)) + .onAction(context -> editDefaultDataTypeSettings(context)) + .buildAndInstall(tool); - settingsAction.setEnabled(true); - tool.addAction(settingsAction); + // Default settings action for composite editor components (Program-based) + new ActionBuilder("Default Settings", getName()) + .sharedKeyBinding() + .popupMenuPath(DATATYPE_SETTINGS_POPUP_PATH) + .popupMenuGroup("Settings") + .withContext(ComponentProgramActionContext.class) + .enabledWhen(context -> isDefaultComponentSettingsAllowed(context)) + .onAction(context -> editDefaultComponentSettings(context)) + .buildAndInstall(tool); - defaultSettingsAction = new DockingAction("Default Data Settings", getName()) { - @Override - public void actionPerformed(ActionContext context) { - defaultDataSettingsCallback((ListingActionContext) context.getContextObject()); - } + // Default settings action for composite editor components (stand-alone archive) + new ActionBuilder("Default Settings", getName()) + .sharedKeyBinding() + .popupMenuPath(DATATYPE_SETTINGS_POPUP_PATH) + .popupMenuGroup("Settings") + .withContext(ComponentStandAloneActionContext.class) + .enabledWhen(context -> isDefaultComponentSettingsAllowed(context)) + .onAction(context -> editDefaultComponentSettings(context)) + .buildAndInstall(tool); - @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - if (contextObject instanceof ListingActionContext) { - return isDataTypeSettingsAllowed((ListingActionContext) context, true); - } - return false; - } - }; - - defaultSettingsAction.setPopupMenuData( - new MenuData(DEFAULT_DATA_SETTINGS_POPUP_PATH, null, "Settings")); - - defaultSettingsAction.setEnabled(true); - tool.addAction(defaultSettingsAction); - - editDataTypeAction = new DockingAction("Edit Data Type", getName()) { - @Override - public void actionPerformed(ActionContext context) { - editDataTypeCallback((ListingActionContext) context.getContextObject()); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - if (contextObject instanceof ListingActionContext) { - DataType editableDt = - getEditableDataTypeFromContext((ListingActionContext) contextObject); + editDataTypeAction = new ActionBuilder("Edit Data Type", getName()) + .popupMenuPath(EDIT_DATA_TYPE_POPUP_PATH) + .popupMenuGroup("BasicData") + .withContext(ListingActionContext.class) + .enabledWhen(c -> { + DataType editableDt = getEditableDataTypeFromContext(c); if (editableDt != null) { editDataTypeAction.setHelpLocation( dtmService.getEditorHelpLocation(editableDt)); return true; } - } - return false; - } - }; - - editDataTypeAction.setPopupMenuData( - new MenuData(EDIT_DATA_TYPE_POPUP_PATH, null, "BasicData")); - - editDataTypeAction.setEnabled(true); - editDataTypeAction.setHelpLocation(new HelpLocation("DataTypeEditors", "Structure_Editor")); - tool.addAction(editDataTypeAction); + return false; + }) + .onAction(c -> editDataTypeCallback(c)) + .helpLocation(new HelpLocation("DataTypeEditors", "Structure_Editor")) + .buildAndInstall(tool); chooseDataTypeAction = new ChooseDataTypeAction(this); chooseDataTypeAction.setEnabled(false); @@ -605,12 +609,105 @@ public class DataPlugin extends Plugin implements DataService { if (data == null) { return; } - dialog = new DataSettingsDialog(context.getProgram(), data); + dialog = new DataSettingsDialog(data); } tool.showDialog(dialog); dialog.dispose(); } + DataType getSelectedDataType(DataTypesActionContext context) { + Object contextObject = context.getContextObject(); + GTree gtree = (GTree) contextObject; + TreePath[] selectionPaths = gtree.getSelectionPaths(); + if (selectionPaths == null || selectionPaths.length != 1) { + return null; + } + DataTypeTreeNode node = (DataTypeTreeNode) selectionPaths[0].getLastPathComponent(); + if (!(node instanceof DataTypeNode)) { + return null; + } + DataTypeNode dataTypeNode = (DataTypeNode) node; + DataType dataType = dataTypeNode.getDataType(); + if (dataType.getDataTypeManager() instanceof DataTypeManagerDB) { + return dataType; + } + return null; + } + + protected void editDefaultDataTypeSettings(DataTypesActionContext context) { + DataType dataType = getSelectedDataType(context); + if (dataType == null) { + return; + } + DataTypeManager dtm = dataType.getDataTypeManager(); + if (!(dtm instanceof DataTypeManagerDB)) { + return; + } + + SettingsDefinition[] settingsDefinitions = dataType.getSettingsDefinitions(); + if (!(dtm instanceof ProgramDataTypeManager)) { + // Non-Program use limited to TypeDefSettingsDefinition only + settingsDefinitions = + SettingsDefinition.filterSettingsDefinitions(settingsDefinitions, def -> { + return (def instanceof TypeDefSettingsDefinition); + }); + } + + DataTypeSettingsDialog dialog = new DataTypeSettingsDialog(dataType, settingsDefinitions); + tool.showDialog(dialog); + dialog.dispose(); + } + + private void editDefaultComponentSettings(ComponentContext context) { + DataTypeSettingsDialog dialog = new DataTypeSettingsDialog(context.getDataTypeComponent()); + tool.showDialog(dialog); + dialog.dispose(); + dialog = null; + } + + protected boolean isDefaultDataTypeSettingsAllowed(DataTypesActionContext context) { + DataType dt = getSelectedDataType(context); + if (dt == null) { + return false; + } + + DataTypeManager dtm = dt.getDataTypeManager(); + if (dtm instanceof BuiltInDataTypeManager) { + return false; // no settings modifications are permitted + } + if ((dt instanceof BuiltIn) && !dtm.allowsDefaultBuiltInSettings()) { + // prevent BuiltIn settings modification when not allowed + return false; + } + + SettingsDefinition[] settingsDefinitions = dt.getSettingsDefinitions(); + if (dtm instanceof ProgramBasedDataTypeManager) { + // Any defined setting may be modified within a Program + return settingsDefinitions.length != 0; + } + + // Non-Program use limited to TypeDefSettingsDefinition modification only + for (SettingsDefinition def : settingsDefinitions) { + if (def instanceof TypeDefSettingsDefinition) { + return true; + } + } + return false; + } + + boolean isDefaultComponentSettingsAllowed(ComponentContext context) { + // Note: targetDtm should not be modified and reflects the ultimate target. + // This context is intended to be used by composite editors where the component + // parent datatype resides within a temporary datatype manager and not the targetDtm + // until a subsequent save/apply is performed when the settings will get copied. + DataTypeManager targetDtm = context.getDataTypeManager(); + if (targetDtm.allowsDefaultComponentSettings()) { + DataType dt = context.getDataTypeComponent().getDataType(); + return dt.getSettingsDefinitions().length != 0; + } + return false; + } + boolean isDataTypeSettingsAllowed(ListingActionContext context, boolean editDefaults) { ProgramSelection selection = context.getSelection(); Data data = getDataUnit(context); @@ -624,7 +721,7 @@ public class DataPlugin extends Plugin implements DataService { return data.getDataType().getSettingsDefinitions().length != 0; } - private void defaultDataSettingsCallback(ListingActionContext context) { + private void editDefaultDataSettings(ListingActionContext context) { // get the structure dt we are over Data data = getDataUnit(context); @@ -632,23 +729,19 @@ public class DataPlugin extends Plugin implements DataService { return; } - DataSettingsDialog dialog = null; - Program program = context.getProgram(); + DataTypeSettingsDialog dialog = null; Data parent = data.getParent(); if (parent != null) { DataType parentDT = parent.getDataType(); if (parentDT instanceof Composite) { int[] path = context.getLocation().getComponentPath(); - - dialog = new DataSettingsDialog(program, + dialog = new DataTypeSettingsDialog( ((Composite) parentDT).getComponent(path[path.length - 1])); } - else { - dialog = new DataSettingsDialog(program, data.getDataType()); - } } - else { - dialog = new DataSettingsDialog(program, data.getDataType()); + if (dialog == null) { + DataType dt = data.getDataType(); + dialog = new DataTypeSettingsDialog(dt, dt.getSettingsDefinitions()); } tool.showDialog(dialog); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataSettingsDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataSettingsDialog.java index 8cc5e5a248..23219458fd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataSettingsDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataSettingsDialog.java @@ -15,230 +15,76 @@ */ package ghidra.app.plugin.core.data; -import java.awt.*; import java.util.ArrayList; import java.util.List; -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.TableCellEditor; - -import docking.DialogComponentProvider; -import docking.widgets.combobox.GComboBox; -import docking.widgets.dialogs.StringChoices; -import docking.widgets.table.AbstractSortedTableModel; -import docking.widgets.table.GTable; import ghidra.docking.settings.*; -import ghidra.program.model.data.Composite; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.DataTypeComponent; +import ghidra.program.model.data.*; import ghidra.program.model.listing.*; import ghidra.program.util.InteriorSelection; import ghidra.program.util.ProgramSelection; import ghidra.util.HelpLocation; import ghidra.util.exception.AssertException; import ghidra.util.exception.CancelledException; -import ghidra.util.table.GhidraTable; import ghidra.util.task.*; -public class DataSettingsDialog extends DialogComponentProvider { +public class DataSettingsDialog extends AbstractSettingsDialog { - private final static int WIDTH = 350; - private final static int HEIGHT = 150; - - private static String[] BOOLEAN_CHOICES = { "yes", "no" }; - private static String NO_CHOICE = ""; - - private String name; - private Data data; // Only set for single data unit mode private ProgramSelection selection; // Only set for data selection mode - private DataType dataType; // not set for data selection mode - private DataTypeComponent dtc; // Only set for single data-type component mode - private SettingsDefinition[] settingsDefs; // required - private Settings defaultSettings; // not set for data selection mode - private SettingsImpl settings; // required - private boolean editingDefaults; - - private SettingsTableModel settingsTableModel; - private GTable settingsTable; - private boolean appliedSettings; + private Data data; // null for selection use private Program program; + /** + * Construct for data instance settings based upon selection + * @param program program which contains data selection + * @param sel data selection + * @throws CancelledException if operation cancelled + */ public DataSettingsDialog(Program program, ProgramSelection sel) throws CancelledException { - super("Data Settings", true, false, true, false); + super("Common Settings for Selected Data", getCommonSettings(program, sel), null); this.program = program; this.selection = sel; - - settingsDefs = getCommonSettings(); - settings = new SettingsImpl(); - setHelpLocation(new HelpLocation("DataPlugin", "Data_Settings_OnSelection")); - - buildPanel(); } - public DataSettingsDialog(Program program, Data data) { - super("Data Settings", true, false, true, false); + /** + * Construct for data instance settings (includes component instance) within a Program + * @param data data whose instance settings are to be modified + */ + public DataSettingsDialog(Data data) { + super(constructTitle(data), + getAllowedDataInstanceSettingsDefinitions(data.getDataType()), data); this.data = data; - this.program = program; - dataType = data.getDataType(); - settingsDefs = dataType.getSettingsDefinitions(); + this.program = data.getProgram(); + + // Set Help for use case - data vs. data-component Data pdata = data.getParent(); - if (pdata != null) { - DataType pdt = pdata.getBaseDataType(); - if (pdt instanceof Composite) { - Composite comp = (Composite) pdt; - this.dtc = comp.getComponent(data.getComponentIndex()); - setHelpLocation(new HelpLocation("DataPlugin", "SettingsOnStructureComponents")); - } - } - if (dtc == null) { - setHelpLocation(new HelpLocation("DataPlugin", "Data_Settings")); - } - - settings = new SettingsImpl(data); - defaultSettings = data.getDefaultSettings(); - settings.setDefaultSettings(defaultSettings); - buildPanel(); - } - - public DataSettingsDialog(Program program, DataType dataType) { - super("Data Settings", true, false, true, false); - this.dataType = dataType; - this.program = program; - editingDefaults = true; - settingsDefs = dataType.getSettingsDefinitions(); - settings = new SettingsImpl(dataType.getDefaultSettings()); - defaultSettings = dataType.getDefaultSettings(); - buildPanel(); - setHelpLocation(new HelpLocation("DataPlugin", "Default_Data_Settings")); - } - - DataSettingsDialog(Program program, DataTypeComponent dtc) { - super("Data Settings", true, false, true, false); - this.dtc = dtc; - this.program = program; - editingDefaults = true; - settingsDefs = dtc.getDataType().getSettingsDefinitions(); - settings = new SettingsImpl(dtc.getDefaultSettings()); - defaultSettings = dtc.getDefaultSettings(); - buildPanel(); - setHelpLocation(new HelpLocation("DataPlugin", "SettingsOnStructureComponents")); - } - - GTable getSettingsTable() { - return settingsTable; - } - - SettingsTableModel getSettingsTableModel() { - return settingsTableModel; - } - - public void dispose() { - close(); - program = null; - data = null; - dataType = null; - dtc = null; - settingsDefs = null; - defaultSettings = null; - settings = null; - } - - boolean hasSettings() { - return settingsDefs.length != 0; - } - - private String constructTitle() { - if (selection != null) { - return "Common Settings for Selected Data"; - } - StringBuffer nameBuf = new StringBuffer(); - if (data == null) { - nameBuf.append("Default "); - } - if (dtc != null) { - nameBuf.append(dtc.getDataType().getDisplayName()); - nameBuf.append(" Settings ("); - nameBuf.append(dtc.getParent().getDisplayName()); - nameBuf.append('.'); - String fname = dtc.getFieldName(); - if (fname == null) { - fname = dtc.getDefaultFieldName(); - } - nameBuf.append(fname); - nameBuf.append(')'); + if (pdata != null && (pdata.getBaseDataType() instanceof Composite)) { + setHelpLocation(new HelpLocation("DataPlugin", "SettingsOnStructureComponents")); } else { - nameBuf.append(dataType.getDisplayName()); - nameBuf.append(" Settings"); + setHelpLocation(new HelpLocation("DataPlugin", "Data_Settings")); } - if (data != null) { - nameBuf.append(" at "); - nameBuf.append(data.getMinAddress().toString()); - } - return nameBuf.toString(); } - private void buildPanel() { - - name = constructTitle(); - setTitle(name); - addWorkPanel(buildWorkPanel()); - addButtons(); + static SettingsDefinition[] getAllowedDataInstanceSettingsDefinitions(DataType dt) { + return SettingsDefinition.filterSettingsDefinitions(dt.getSettingsDefinitions(), def -> { + return !(def instanceof TypeDefSettingsDefinition); + }); } - private void addButtons() { - - addOKButton(); - - JButton newApplyButton = new JButton("Apply"); - newApplyButton.addActionListener(e -> applySettings()); - addButton(newApplyButton); - - addCancelButton(); - } - - private JPanel buildWorkPanel() { - JPanel workPanel = new JPanel(new BorderLayout()); - workPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); - - settingsTableModel = new SettingsTableModel(settingsDefs); - settingsTableModel.addTableModelListener(e -> appliedSettings = false); - settingsTable = new GhidraTable(settingsTableModel); - settingsTable.setAutoscrolls(true); - settingsTable.setRowSelectionAllowed(false); - settingsTable.setColumnSelectionAllowed(false); - - // disable user sorting and column adding (we don't expect enough data to require sort - // changes) - settingsTable.getTableHeader().setReorderingAllowed(false); - settingsTable.setColumnHeaderPopupEnabled(false); - settingsTable.setUserSortingEnabled(false); - - settingsTable.setDefaultRenderer(Settings.class, new DefaultTableCellRenderer()); - settingsTable.setDefaultEditor(Settings.class, new SettingsEditor()); - - JScrollPane scrollpane = new JScrollPane(settingsTable); - scrollpane.setPreferredSize(new Dimension(WIDTH, HEIGHT)); - - workPanel.add(scrollpane, BorderLayout.CENTER); - - return workPanel; + private static String constructTitle(Data data) { + StringBuffer buffy = new StringBuffer( + DataTypeSettingsDialog.constructTitle(null, data.getDataType(), false)); + buffy.append(" at "); + buffy.append(data.getMinAddress().toString()); + return buffy.toString(); } @Override - protected void cancelCallback() { - close(); - dispose(); - } - - @Override - protected void okCallback() { - applySettings(); - close(); - dispose(); + public void dispose() { + program = null; + super.dispose(); } /** @@ -249,17 +95,22 @@ public class DataSettingsDialog extends DialogComponentProvider { * flag being set. * */ - private class CommonSettingsAccumulator extends Task { + private static class CommonSettingsAccumulatorTask extends Task { + + Program program; + ProgramSelection selection; - boolean cancelled = false; SettingsDefinition[] defsArray = new SettingsDefinition[0]; - CommonSettingsAccumulator() { + CommonSettingsAccumulatorTask(Program program, ProgramSelection selection) { super("Accumulating Data Settings", true, false, true); + this.program = program; + this.selection = selection; } @Override - public void run(TaskMonitor monitor) { + public void run(TaskMonitor monitor) throws CancelledException { + monitor.initialize(selection.getNumAddresses()); InteriorSelection interiorSelection = selection.getInteriorSelection(); if (interiorSelection != null) { accumulateInteriorSettingsDefinitions(interiorSelection, monitor); @@ -269,7 +120,8 @@ public class DataSettingsDialog extends DialogComponentProvider { } } - private void accumulateDataSettingsDefinitions(TaskMonitor monitor) { + private void accumulateDataSettingsDefinitions(TaskMonitor monitor) + throws CancelledException { List> defClasses = new ArrayList<>(); List defs = new ArrayList<>(); @@ -280,26 +132,28 @@ public class DataSettingsDialog extends DialogComponentProvider { return; } Data d = definedData.next(); + monitor.incrementProgress(d.getLength()); for (SettingsDefinition def : d.getDataType().getSettingsDefinitions()) { + if (def instanceof TypeDefSettingsDefinition) { + continue; // default-use-only settings not supported + } defs.add(def); defClasses.add(def.getClass()); } while (!defClasses.isEmpty() && definedData.hasNext()) { - if (monitor.isCancelled()) { - cancelled = true; - return; - } + monitor.checkCanceled(); d = definedData.next(); removeMissingDefinitions(defClasses, defs, d.getDataType().getSettingsDefinitions()); + monitor.incrementProgress(d.getLength()); } defsArray = new SettingsDefinition[defs.size()]; defs.toArray(defsArray); } private void accumulateInteriorSettingsDefinitions(InteriorSelection interiorSelection, - TaskMonitor monitor) { + TaskMonitor monitor) throws CancelledException { List> defClasses = null; List defs = null; @@ -315,10 +169,12 @@ public class DataSettingsDialog extends DialogComponentProvider { int fromIndex = from[from.length - 1]; int toIndex = to[to.length - 1]; for (int i = fromIndex; i <= toIndex; i++) { + monitor.checkCanceled(); dataComp = parent.getComponent(i); if (dataComp == null) { break; } + monitor.incrementProgress(dataComp.getLength()); DataType dt = dataComp.getDataType(); if (dt == DataType.DEFAULT) { continue; @@ -344,13 +200,11 @@ public class DataSettingsDialog extends DialogComponentProvider { } } - private SettingsDefinition[] getCommonSettings() throws CancelledException { - - CommonSettingsAccumulator myTask = new CommonSettingsAccumulator(); - - new TaskLauncher(myTask, getComponent()); - - if (myTask.cancelled) { + private static SettingsDefinition[] getCommonSettings(Program program, + ProgramSelection selection) throws CancelledException { + CommonSettingsAccumulatorTask myTask = new CommonSettingsAccumulatorTask(program, selection); + new TaskLauncher(myTask, null); + if (myTask.isCancelled()) { throw new CancelledException(); } return myTask.defsArray; @@ -376,52 +230,70 @@ public class DataSettingsDialog extends DialogComponentProvider { } } - private void applyCommonSettings() { + private static class ApplyCommonSettingsTask extends Task { - // TODO: Use task since this could be big and slow + DataSettingsDialog dlg; + Program program; + ProgramSelection selection; - InteriorSelection interiorSelection = selection.getInteriorSelection(); - if (interiorSelection == null) { - CodeUnitIterator codeUnits = program.getListing().getCodeUnits(selection, true); - while (codeUnits.hasNext()) { - // TODO: check monitor - CodeUnit cu = codeUnits.next(); - if ((cu instanceof Data) && ((Data) cu).isDefined()) { - applySettingsToData((Data) cu); + ApplyCommonSettingsTask(DataSettingsDialog dlg, Program program, ProgramSelection selection) { + super("Applying Settings", true, false, true); + this.dlg = dlg; + this.program = program; + this.selection = selection; + } + + @Override + public void run(TaskMonitor monitor) throws CancelledException { + + monitor.initialize(selection.getNumAddresses()); + + InteriorSelection interiorSelection = selection.getInteriorSelection(); + if (interiorSelection == null) { + DataIterator definedData = program.getListing().getDefinedData(selection, true); + while (definedData.hasNext()) { + monitor.checkCanceled(); + Data d = definedData.next(); + applySettingsToData(dlg, d); + monitor.incrementProgress(d.getLength()); } + return; } - return; - } - int[] from = interiorSelection.getFrom().getComponentPath(); - int[] to = interiorSelection.getTo().getComponentPath(); + int[] from = interiorSelection.getFrom().getComponentPath(); + int[] to = interiorSelection.getTo().getComponentPath(); - Data dataComp = DataPlugin.getDataUnit(program, selection.getMinAddress(), from); - if (dataComp == null) { - return; - } - Data parent = dataComp.getParent(); - int fromIndex = from[from.length - 1]; - int toIndex = to[to.length - 1]; - for (int i = fromIndex; i <= toIndex; i++) { - dataComp = parent.getComponent(i); + Data dataComp = DataPlugin.getDataUnit(program, selection.getMinAddress(), from); if (dataComp == null) { - break; + return; } - DataType dt = dataComp.getDataType(); - if (dt == DataType.DEFAULT) { - continue; + Data parent = dataComp.getParent(); + int fromIndex = from[from.length - 1]; + int toIndex = to[to.length - 1]; + + monitor.initialize(toIndex - fromIndex + 1); + for (int i = fromIndex; i <= toIndex; i++) { + monitor.checkCanceled(); + dataComp = parent.getComponent(i); + if (dataComp == null) { + break; + } + monitor.incrementProgress(dataComp.getLength()); + DataType dt = dataComp.getDataType(); + if (dt == DataType.DEFAULT) { + continue; + } + applySettingsToData(dlg, dataComp); } - applySettingsToData(dataComp); } + } - private void applySettingsToData(Data dataTarget) { - if (appliedSettings) { - return; - } - for (SettingsDefinition settingsDef : settingsDefs) { - if (selection != null && settings.getValue(settingsDef.getName()) == null) { + private static void applySettingsToData(DataSettingsDialog dlg, Data dataTarget) { + Settings settings = dlg.getSettings(); + Settings defaultSettings = dlg.getDefaultSettings(); // may be null + for (SettingsDefinition settingsDef : dlg.getSettingsDefinitions()) { + if (dlg.selection != null && !settingsDef.hasValue(settings)) { continue; // No-Choice } @@ -446,333 +318,41 @@ public class DataSettingsDialog extends DialogComponentProvider { def.setValue(dataTarget, s); } } + else if (settingsDef instanceof NumberSettingsDefinition) { + NumberSettingsDefinition def = (NumberSettingsDefinition) settingsDef; + long val = def.getValue(settings); + if (defaultSettings != null && val == def.getValue(defaultSettings)) { + def.clear(dataTarget); + } + else { + def.setValue(dataTarget, val); + } + } else { throw new AssertException(); } } } - private void applySettings() { - int txId = program.startTransaction(name); - boolean success = true; + protected void applySettings() throws CancelledException { + int txId = program.startTransaction(getTitle()); try { if (selection != null) { - applyCommonSettings(); - appliedSettings = true; - } - else if (data != null) { - applySettingsToData(data); - appliedSettings = true; - } - else { - Settings origDefSettings = null; - if (dataType != null) { - origDefSettings = dataType.getDefaultSettings(); - } - else { - origDefSettings = dtc.getDefaultSettings(); - } -// String[] names = settings.getNames(); -// for (int i=0; i"; - } - - boolean useDefault() { - if (definition instanceof EnumSettingsDefinition) { - EnumSettingsDefinition def = (EnumSettingsDefinition) definition; - return def.getChoice(settings) == def.getChoice(defaultSettings); - } - else if (definition instanceof BooleanSettingsDefinition) { - BooleanSettingsDefinition def = (BooleanSettingsDefinition) definition; - return def.getValue(settings) == def.getValue(defaultSettings); - } - return false; - } - - boolean setSettingsChoice(Object value) { - if (definition instanceof EnumSettingsDefinition) { - setChoice(value, (EnumSettingsDefinition) definition); - return true; - } - else if (definition instanceof BooleanSettingsDefinition) { - setChoice(value, (BooleanSettingsDefinition) definition); - return true; - } - return false; - } - - void clear(SettingsImpl s) { - definition.clear(s); - } - } - - private class SettingsTableModel extends AbstractSortedTableModel { - - private List rows = new ArrayList<>(); - - SettingsTableModel(SettingsDefinition[] settingsDefs) { - for (SettingsDefinition sd : settingsDefs) { - rows.add(new SettingsRowObject(sd)); - } - } - - @Override - public List getModelData() { - return rows; - } - - @Override - public String getName() { - return "Settings Definition Model"; - } - - @Override - public boolean isSortable(int columnIndex) { - return columnIndex == 0; - } - - @Override - public boolean isCellEditable(int row, int col) { - return col != 0; - } - - @Override - public int getColumnCount() { - return (selection != null || editingDefaults) ? 2 : 3; - } - - @Override - public String getColumnName(int col) { - switch (col) { - case 0: - return "Name"; - case 1: - return "Settings"; - case 2: - return "Use Default"; - } - return null; - } - - // override this to force the correct cell editors to be used - @Override - public Class getColumnClass(int col) { - switch (col) { - case 0: - return String.class; - case 1: - return Settings.class; - case 2: - return Boolean.class; - } - return null; - } - - @Override - public Object getColumnValueForRow(SettingsRowObject t, int columnIndex) { - switch (columnIndex) { - case 0: - return t.getName(); - case 1: - return t.getSettingsChoices(); - case 2: - return t.useDefault(); - } - return null; - } - - @Override - public void setValueAt(Object value, int row, int col) { - SettingsRowObject rowObject = rows.get(row); - switch (col) { - case 1: - if (rowObject.setSettingsChoice(value)) { - fireTableDataChanged(); - } - break; - case 2: - if (((Boolean) value).booleanValue()) { - rowObject.clear(settings); - fireTableDataChanged(); - } - break; - } - } - } - - class SettingsEditor extends AbstractCellEditor implements TableCellEditor { - - final static int ENUM = 0; - final static int BOOLEAN = 1; - - private int mode; - private GComboBox comboBox = new GComboBox<>(); - - SettingsEditor() { - comboBox.addItemListener(e -> fireEditingStopped()); - } - - GComboBox getComboBox() { - return comboBox; - } - - @Override - public Object getCellEditorValue() { - switch (mode) { - case ENUM: - return getComboBoxEnum(); - case BOOLEAN: - return getComboBoxEnum(); - } - throw new AssertException(); - } - - private StringChoices getComboBoxEnum() { - String[] items = new String[comboBox.getItemCount()]; - for (int i = 0; i < items.length; i++) { - items[i] = comboBox.getItemAt(i); - } - StringChoices enuum = new StringChoices(items); - enuum.setSelectedValue(comboBox.getSelectedIndex()); - return enuum; - } - - @Override - public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, - int row, int column) { - if (value instanceof StringChoices || value instanceof Boolean) { - initComboBox((StringChoices) value); - return comboBox; - } - throw new AssertException( - "SettingsEditor: " + value.getClass().getName() + " not supported"); - } - - private void initComboBox(StringChoices enuum) { - mode = ENUM; - comboBox.removeAllItems(); - String[] items = enuum.getValues(); - for (String item : items) { - comboBox.addItem(item); - } - comboBox.setSelectedIndex(enuum.getSelectedValueIndex()); - } - - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataTypeSettingsDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataTypeSettingsDialog.java new file mode 100644 index 0000000000..66a8429673 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataTypeSettingsDialog.java @@ -0,0 +1,154 @@ +/* ### + * 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.data; + +import ghidra.docking.settings.Settings; +import ghidra.docking.settings.SettingsDefinition; +import ghidra.program.database.data.DataTypeManagerDB; +import ghidra.program.model.data.*; +import ghidra.util.HelpLocation; + +public class DataTypeSettingsDialog extends AbstractSettingsDialog { + + private DataType dataType; // not set for data selection mode + private DataTypeComponent dtc; // Only set for single data-type component mode + + /** + * Construct for data type default settings + * @param dataType data type (must be resolved to program) + * @param settingsDefinitions settings definitions to be displayed (may be a restricted set) + */ + public DataTypeSettingsDialog(DataType dataType, SettingsDefinition[] settingsDefinitions) { + super(constructTitle(null, dataType, true), settingsDefinitions, + dataType.getDefaultSettings()); + checkDataType(dataType); + this.dataType = dataType; + setHelpLocation(new HelpLocation("DataPlugin", "Default_Settings")); + } + + /** + * Construct for structure component default settings + * @param dtc data type component (must belong to program-resolved structure) + */ + DataTypeSettingsDialog(DataTypeComponent dtc) { + super(constructTitle(dtc, dtc.getDataType(), true), + DataSettingsDialog.getAllowedDataInstanceSettingsDefinitions(dtc.getDataType()), + dtc.getDefaultSettings()); + // NOTE: component default settings currently use the same restricted set of definitions + checkDataType(dtc.getParent()); + this.dtc = dtc; + this.dataType = dtc.getDataType(); + setHelpLocation(new HelpLocation("DataPlugin", "SettingsOnStructureComponents")); + } + + private static void checkDataType(DataType dt) { + DataTypeManager dtm = dt.getDataTypeManager(); + if (dtm instanceof BuiltInDataTypeManager) { + throw new IllegalArgumentException( + "Unsupported use for datatype from BuiltInDataTypeManager"); + } + if (dtm instanceof DataTypeManagerDB) { + long id = dtm.getID(dt); + if (id > 0) { + if (dt == dtm.getDataType(id)) { + return; // valid original instance + } + } + } + throw new IllegalArgumentException("Invalid data type instance"); + } + + @Override + public void dispose() { + super.dispose(); + dataType = null; + dtc = null; + } + + static String constructTitle(DataTypeComponent dtc, DataType dataType, boolean isDefault) { + // TODO: May need to truncate names which could be very long + StringBuffer nameBuf = new StringBuffer(); + if (isDefault) { + nameBuf.append("Default "); + } + String name = dataType.getDisplayName(); + // default array settings defer to base type + if (dtc == null) { + name = getSettingsBaseType(dataType).getDisplayName(); + } + nameBuf.append(name); + nameBuf.append(" Settings"); + if (dtc != null) { + nameBuf.append(" ("); + nameBuf.append(dtc.getParent().getDisplayName()); + nameBuf.append('.'); + String fname = dtc.getFieldName(); + if (fname == null) { + fname = dtc.getDefaultFieldName(); + } + nameBuf.append(fname); + nameBuf.append(')'); + } + return nameBuf.toString(); + } + + /** + * Get base datatype associated with any array (include typedef of array) + * @param dt datatype + * @return base array datatype or specified dt if not an array type + */ + private static DataType getSettingsBaseType(DataType dt) { + while (true) { + if (dt instanceof TypeDef) { + DataType baseDt = ((TypeDef) dt).getBaseDataType(); + if (baseDt instanceof Array) { + dt = baseDt; + } + else { + break; + } + } + else if (dt instanceof Array) { + dt = ((Array) dt).getDataType(); + } + else { + break; + } + } + return dt; + } + + protected void applySettings() { + DataTypeManager dtm = dataType.getDataTypeManager(); + int txId = dtm.startTransaction(getTitle()); + try { + Settings origDefSettings = null; + if (dtc != null) { + origDefSettings = dtc.getDefaultSettings(); + } + else { + origDefSettings = dataType.getDefaultSettings(); + } + Settings modifiedSettings = getSettings(); + for (SettingsDefinition settingsDef : getSettingsDefinitions()) { + settingsDef.copySetting(modifiedSettings, origDefSettings); + } + } + finally { + dtm.endTransaction(txId, true); + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/RenameDataFieldAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/RenameDataFieldAction.java index 3e1cf2f6d4..0729a06c2f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/RenameDataFieldAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/RenameDataFieldAction.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +15,12 @@ */ package ghidra.app.plugin.core.data; +import java.awt.event.KeyEvent; + +import docking.action.KeyBindingData; +import docking.action.MenuData; import ghidra.app.context.ListingActionContext; +import ghidra.app.context.ListingContextAction; import ghidra.app.util.PluginConstants; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.data.*; @@ -24,27 +28,19 @@ import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Program; import ghidra.program.util.FieldNameFieldLocation; import ghidra.program.util.ProgramLocation; - -import java.awt.event.KeyEvent; - -import javax.swing.KeyStroke; - -import docking.ActionContext; -import docking.action.*; /** * Base class for comment actions to edit and delete comments. */ -class RenameDataFieldAction extends DockingAction { +class RenameDataFieldAction extends ListingContextAction { - private static final KeyStroke KEY_BINDING = KeyStroke.getKeyStroke(KeyEvent.VK_N,0); private DataPlugin plugin; private RenameDataFieldDialog dialog; public RenameDataFieldAction(DataPlugin plugin) { super("Rename Data Field", plugin.getName()); dialog = new RenameDataFieldDialog(plugin); -// ACTIONS - auto generated + setPopupMenuData( new MenuData( new String[] {"Data", "Rename Field"},null,"BasicData" ) ); @@ -56,11 +52,8 @@ class RenameDataFieldAction extends DockingAction { setEnabled(true); } - /** - * Method called when the action is invoked. - */ @Override - public void actionPerformed(ActionContext context) { + protected void actionPerformed(ListingActionContext context) { ListingActionContext programActionContext = (ListingActionContext) context.getContextObject(); PluginTool tool = plugin.getTool(); Program program = programActionContext.getProgram(); @@ -87,17 +80,9 @@ class RenameDataFieldAction extends DockingAction { } } - /* - * @see docking.DockableAction#isValidContext(java.lang.Object) - */ - @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - if (!(contextObject instanceof ListingActionContext)) { - return false; - } - ListingActionContext programActionContext = (ListingActionContext) contextObject; - return (programActionContext.getLocation() instanceof FieldNameFieldLocation); + @Override + protected boolean isEnabledForContext(ListingActionContext context) { + return (context.getLocation() instanceof FieldNameFieldLocation); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/OpenDomainFileTask.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/OpenDomainFileTask.java index 7cff45f0c7..69ee117299 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/OpenDomainFileTask.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/OpenDomainFileTask.java @@ -125,6 +125,11 @@ class OpenDomainFileTask extends Task { return version == otherVersion; } + /** + * Open archive in an immutable fashion. Unlike ProgramDB, we do not want to + * allow upgrade or modification of a read-only archve (e.g., not-checked-out). + * @param monitor task monitor + */ private void openReadOnlyFile(TaskMonitor monitor) { String fileDescr = ((version != DomainFile.DEFAULT_VERSION) ? "version " + version + " of " : "") + @@ -134,7 +139,7 @@ class OpenDomainFileTask extends Task { monitor.setMessage("Opening " + fileDescr); contentType = domainFile.getContentType(); dtArchive = - (DataTypeArchive) domainFile.getReadOnlyDomainObject(this, version, monitor); + (DataTypeArchive) domainFile.getImmutableDomainObject(this, version, monitor); } catch (CancelledException e) { // we don't care, the task has been canceled diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeUtils.java index a611966ae3..fa2a72fb2a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeUtils.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeUtils.java @@ -22,8 +22,8 @@ import java.util.List; import javax.swing.Icon; import javax.swing.ImageIcon; -import ghidra.app.services.DataTypeQueryService; import ghidra.app.services.DataTypeManagerService; +import ghidra.app.services.DataTypeQueryService; import ghidra.program.model.data.*; import ghidra.program.model.data.Enum; import ghidra.util.Msg; @@ -382,7 +382,7 @@ public class DataTypeUtils { *
If "INT" is a typedef on a "dword" then INT[7][3] would have a base data type of dword. * If you wanted to get the INT from INT[7][3] * you should call getNamedBasedDataType(DataType) instead. - * @param baseDataType the data type whose base data type is to be determined. + * @param dt the data type whose base data type is to be determined. * @return the base data type. */ public static DataType getBaseDataType(DataType dt) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datapreview/DataTypeComponentPreview.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datapreview/DataTypeComponentPreview.java index 734725382b..3bcedcdcf3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datapreview/DataTypeComponentPreview.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datapreview/DataTypeComponentPreview.java @@ -18,7 +18,6 @@ */ package ghidra.app.plugin.core.datapreview; -import ghidra.docking.settings.SettingsImpl; import ghidra.program.model.address.Address; import ghidra.program.model.data.*; import ghidra.program.model.mem.*; @@ -71,7 +70,7 @@ class DataTypeComponentPreview implements Preview { addr = addr.add(dtc.getOffset()); MemBuffer mb = new DumbMemBufferImpl(memory, addr); DataType dt = dtc.getDataType(); - return dt.getRepresentation(mb, new SettingsImpl(), dtc.getLength()); + return dt.getRepresentation(mb, dtc.getDefaultSettings(), dtc.getLength()); } catch (Exception e) { return "ERROR: unable to create preview"; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datapreview/DataTypePreview.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datapreview/DataTypePreview.java index 274253082b..89bec3f921 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datapreview/DataTypePreview.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datapreview/DataTypePreview.java @@ -18,7 +18,6 @@ */ package ghidra.app.plugin.core.datapreview; -import ghidra.docking.settings.SettingsImpl; import ghidra.program.model.address.Address; import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataTypeInstance; @@ -48,7 +47,7 @@ class DataTypePreview implements Preview { } int length = Math.min(dti.getLength(), MAX_PREVIEW_LENGTH); - return dt.getRepresentation(mb, new SettingsImpl(), length); + return dt.getRepresentation(mb, dt.getDefaultSettings(), length); } catch (Exception e) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/EditMemoryReferencePanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/EditMemoryReferencePanel.java index 246d628006..bd395dbb7f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/EditMemoryReferencePanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/references/EditMemoryReferencePanel.java @@ -239,7 +239,7 @@ class EditMemoryReferencePanel extends EditReferencePanel { populateRefTypes(rt); refTypes.setSelectedItem(rt); - if (fromSubIndex < 0) { + if (fromOpIndex < 0) { Program program = plugin.getCurrentProgram(); ProgramLocation location = plugin.getCurrentLocation(); Address toAddr = null; 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 a094c47d91..af7ec49d64 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 @@ -22,7 +22,6 @@ import javax.swing.JOptionPane; import docking.widgets.OptionDialog; import docking.widgets.fieldpanel.support.FieldRange; import docking.widgets.fieldpanel.support.FieldSelection; - /** * Function stack editor model for maintaining information about the edits to * a function stack frame. Updates the stack frame with the edit changes. @@ -33,7 +32,6 @@ 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.CompositeEditorModel; import ghidra.app.plugin.core.compositeeditor.DataTypeHelper; import ghidra.app.util.datatype.EmptyCompositeException; @@ -89,7 +87,7 @@ public class StackEditorModel extends CompositeEditorModel { return false; } - void stackChangedExcternally(boolean changed) { + void stackChangedExternally(boolean changed) { stackChangedExternally = changed; } @@ -97,13 +95,17 @@ public class StackEditorModel extends CompositeEditorModel { originalStack = function.getStackFrame(); StackFrameDataType stackFrameDataType = new StackFrameDataType(originalStack, dtm); stackFrameDataType.setCategoryPath(dtm.getRootCategory().getCategoryPath()); - load(stackFrameDataType, false); + load(stackFrameDataType); } @Override - public void load(Composite dataType, boolean useOffLineCategory) { - stackChangedExcternally(false); - super.load(dataType, useOffLineCategory); + public void load(Composite dataType) { + stackChangedExternally(false); + super.load(dataType); + } + + protected Composite createViewCompositeFromOriginalComposite(Composite original) { + return (Composite) original.copy(original.getDataTypeManager()); } StackFrameDataType getViewComposite() { @@ -1023,7 +1025,7 @@ public class StackEditorModel extends CompositeEditorModel { newSv.setComment(comment); } } - load(new StackFrameDataType(original, dtm), false); + load(new StackFrameDataType(original, dtm)); clearStatus(); return true; } @@ -1183,7 +1185,6 @@ public class StackEditorModel extends CompositeEditorModel { originalDataTypePath.getDataTypeName().equals(newPath.getDataTypeName()) && originalDataTypePath.getCategoryPath().equals(oldPath.getCategoryPath())) { originalDataTypePath = newPath; - originalCategoryChanged(); compositeInfoChanged(); } } @@ -1241,7 +1242,7 @@ public class StackEditorModel extends CompositeEditorModel { @Override protected Composite getOriginalComposite() { // This is to allow the stack editor panel to have access. - return super.getOriginalComposite(); + return originalComposite; // not contained within datatype manager } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java index 2561edee13..14eaf38463 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/stackeditor/StackEditorPanel.java @@ -303,7 +303,7 @@ public class StackEditorPanel extends CompositeEditorPanel { cancelCellEditing(); // TODO // boolean lockState = model.isLocked(); // save the lock state - model.load(originalDt, model.isOffline()); // reload the structure + model.load(originalDt); // reload the structure // model.setLocked(lockState); // restore the lock state model.updateAndCheckChangeState(); } 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 9a20258803..8d8ea5a667 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 @@ -15,6 +15,10 @@ */ package ghidra.app.plugin.core.stackeditor; +import java.awt.event.MouseEvent; + +import docking.ActionContext; +import ghidra.app.context.ProgramActionContext; import ghidra.app.plugin.core.compositeeditor.*; import ghidra.framework.model.*; import ghidra.framework.plugintool.Plugin; @@ -104,6 +108,11 @@ public class StackEditorProvider extends CompositeEditorProvider implements Doma //@formatter:on } + @Override + public ActionContext getActionContext(MouseEvent event) { + return new ProgramActionContext(this, program); + } + /** * Gets the function name for the function stack frame being edited. * @return the name @@ -240,7 +249,7 @@ public class StackEditorProvider extends CompositeEditorProvider implements Doma stackModel.load(function); } else { - stackModel.stackChangedExcternally(true); + stackModel.stackChangedExternally(true); editorPanel.setStatus("Stack may have been changed externally--data may be stale."); } } 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 2761def8c1..662ce57779 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 @@ -68,7 +68,6 @@ public class StackFrameDataType extends BiDirectionDataType { this.growsNegative = stackDt.growsNegative; this.returnAddressOffset = stackDt.returnAddressOffset; this.stack = stackDt.stack; - this.defaultSettings = stackDt.defaultSettings; for (DataTypeComponentImpl dtc : stackDt.components) { replaceAtOffset(dtc.getOffset(), dtc.getDataType(), dtc.getLength(), dtc.getFieldName(), dtc.getComment()); @@ -150,6 +149,14 @@ public class StackFrameDataType extends BiDirectionDataType { @Override public StackFrameDataType clone(DataTypeManager dtm) { + if (dtm == dataMgr) { + return this; + } + return new StackFrameDataType(this, dtm); + } + + @Override + public DataType copy(DataTypeManager dtm) { return new StackFrameDataType(this, dtm); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsPlugin.java index e9f54b7a95..1f949249cc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsPlugin.java @@ -23,11 +23,13 @@ import ghidra.app.CorePluginPackage; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.core.data.DataSettingsDialog; +import ghidra.app.plugin.core.data.DataTypeSettingsDialog; import ghidra.app.services.GoToService; import ghidra.framework.model.*; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.program.model.data.DataType; import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Program; import ghidra.program.util.*; @@ -62,8 +64,6 @@ public class ViewStringsPlugin extends ProgramPlugin implements DomainObjectList ResourceManager.getDisabledIcon(Icons.REFRESH_ICON, 60); private DockingAction refreshAction; - private DockingAction showSettingsAction; - private DockingAction showDefaultSettingsAction; private SelectionNavigationAction linkNavigationAction; private ViewStringsProvider provider; private SwingUpdateManager reloadUpdateMgr; @@ -111,13 +111,14 @@ public class ViewStringsPlugin extends ProgramPlugin implements DomainObjectList linkNavigationAction = new SelectionNavigationAction(this, provider.getTable()); tool.addLocalAction(provider, linkNavigationAction); - showSettingsAction = new DockingAction("Settings", getName()) { + DockingAction editDataSettingsAction = + new DockingAction("Data Settings", getName(), KeyBindingType.SHARED) { @Override public void actionPerformed(ActionContext context) { try { DataSettingsDialog dialog = provider.getSelectedRowCount() == 1 - ? new DataSettingsDialog(currentProgram, provider.getSelectedData()) - : new DataSettingsDialog(currentProgram, provider.selectData()); + ? new DataSettingsDialog(provider.getSelectedData()) + : new DataSettingsDialog(currentProgram, provider.getProgramSelection()); tool.showDialog(dialog); dialog.dispose(); @@ -128,33 +129,47 @@ public class ViewStringsPlugin extends ProgramPlugin implements DomainObjectList } }; - showSettingsAction.setPopupMenuData(new MenuData(new String[] { "Settings..." }, "R")); - showSettingsAction.setDescription("Shows settings for the selected strings"); - showSettingsAction.setHelpLocation(new HelpLocation("DataPlugin", "Data_Settings")); - showDefaultSettingsAction = new DockingAction("Default Settings", getName()) { + editDataSettingsAction.setPopupMenuData(new MenuData(new String[] { "Settings..." }, "R")); + editDataSettingsAction.setHelpLocation(new HelpLocation("DataPlugin", "Data_Settings")); + + DockingAction editDefaultSettingsAction = + new DockingAction("Default Settings", getName(), KeyBindingType.SHARED) { @Override public void actionPerformed(ActionContext context) { - Data data = provider.getSelectedData(); - DataSettingsDialog dataSettingsDialog = - new DataSettingsDialog(getCurrentProgram(), data.getDataType()); + DataType dt = getSelectedDataType(); + if (dt == null) { + return; + } + DataTypeSettingsDialog dataSettingsDialog = + new DataTypeSettingsDialog(dt, dt.getSettingsDefinitions()); tool.showDialog(dataSettingsDialog); dataSettingsDialog.dispose(); } @Override public boolean isEnabledForContext(ActionContext context) { - return provider.getSelectedRowCount() == 1; + if (provider.getSelectedRowCount() != 1) { + return false; + } + DataType dt = getSelectedDataType(); + if (dt == null) { + return false; + } + return dt.getSettingsDefinitions().length != 0; + } + + private DataType getSelectedDataType() { + Data data = provider.getSelectedData(); + return data != null ? data.getDataType() : null; } }; - showDefaultSettingsAction.setPopupMenuData( + editDefaultSettingsAction.setPopupMenuData( new MenuData(new String[] { "Default Settings..." }, "R")); - showDefaultSettingsAction.setDescription( - "Shows settings for the selected string data type"); - showDefaultSettingsAction.setHelpLocation( - new HelpLocation("DataPlugin", "Default_Data_Settings")); + editDefaultSettingsAction.setHelpLocation( + new HelpLocation("DataPlugin", "Default_Settings")); - tool.addLocalAction(provider, showSettingsAction); - tool.addLocalAction(provider, showDefaultSettingsAction); + tool.addLocalAction(provider, editDataSettingsAction); + tool.addLocalAction(provider, editDefaultSettingsAction); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsProvider.java index 2fa97d67a0..0b2a0245b0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsProvider.java @@ -176,7 +176,7 @@ public class ViewStringsProvider extends ComponentProviderAdapter { tool.contextChanged(this); } - ProgramSelection selectData() { + ProgramSelection getProgramSelection() { return table.getProgramSelection(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/datatype/microsoft/GuidUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/datatype/microsoft/GuidUtil.java index cf5574a3c7..9587fceeb9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/datatype/microsoft/GuidUtil.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/datatype/microsoft/GuidUtil.java @@ -319,7 +319,7 @@ public class GuidUtil { } GuidDataType dt = new GuidDataType(); String guidRep = dt.getRepresentation(new DumbMemBufferImpl(program.getMemory(), address), - new SettingsImpl(), -1); + SettingsImpl.NO_SETTINGS, -1); return guidRep.endsWith(guidString); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/CompositeDataTypeHTMLRepresentation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/CompositeDataTypeHTMLRepresentation.java index 6f1b2e9e61..138941c455 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/CompositeDataTypeHTMLRepresentation.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/CompositeDataTypeHTMLRepresentation.java @@ -175,8 +175,7 @@ public class CompositeDataTypeHTMLRepresentation extends HTMLDataTypeRepresentat BR); append(fullHtml, truncatedHtml, lineCount, LENGTH_PREFIX, footerText.getText(), - BR); - append(fullHtml, truncatedHtml, lineCount, BR, BR); + BR, BR); //@formatter:on // header @@ -343,15 +342,6 @@ public class CompositeDataTypeHTMLRepresentation extends HTMLDataTypeRepresentat return wrapped; } - protected static StringBuilder addAlignmentValue(String alignmentValueString, - StringBuilder buffer) { - - buffer.append(BR); - buffer.append(ALIGNMENT_VALUE_PREFIX + alignmentValueString); - - return buffer; - } - // overridden to return truncated text by default @Override public String getHTMLString() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/PointerDataTypeHTMLRepresentation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/PointerDataTypeHTMLRepresentation.java index 3cc222133c..49f067c883 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/PointerDataTypeHTMLRepresentation.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/PointerDataTypeHTMLRepresentation.java @@ -45,7 +45,7 @@ public class PointerDataTypeHTMLRepresentation extends HTMLDataTypeRepresentatio return truncatedHtmlData; } - private static String buildHTMLText(Pointer pointer, boolean trim) { + static String buildHTMLText(Pointer pointer, boolean trim) { DataType baseDataType = pointer; while (baseDataType instanceof Pointer) { @@ -116,6 +116,10 @@ public class PointerDataTypeHTMLRepresentation extends HTMLDataTypeRepresentatio description = Character.toUpperCase(firstChar) + description.substring(1); } + int length = pointer.getLength(); + description += BR; + description += "Size: " + (length >= 0 ? length : "default"); + return description; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/TypeDefDataTypeHTMLRepresentation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/TypeDefDataTypeHTMLRepresentation.java index e2e2d41826..a60b5fdfb9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/TypeDefDataTypeHTMLRepresentation.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/html/TypeDefDataTypeHTMLRepresentation.java @@ -21,6 +21,8 @@ import java.util.*; import ghidra.app.util.ToolTipUtils; import ghidra.app.util.html.diff.DataTypeDiff; import ghidra.app.util.html.diff.DataTypeDiffBuilder; +import ghidra.docking.settings.Settings; +import ghidra.docking.settings.SettingsDefinition; import ghidra.program.model.data.*; import ghidra.util.HTMLUtilities; import ghidra.util.StringUtilities; @@ -127,6 +129,47 @@ public class TypeDefDataTypeHTMLRepresentation extends HTMLDataTypeRepresentatio baseDataType = ((Pointer) baseDataType).getDataType(); } } + + // Show modified default settings details + StringBuilder buffy = new StringBuilder(); + Settings defaultSettings = typeDef.getDefaultSettings(); + HashSet> ignoredSettings = new HashSet<>(); + + for (SettingsDefinition settingsDef : typeDef.getSettingsDefinitions()) { + if (!(settingsDef instanceof TypeDefSettingsDefinition) || + !settingsDef.hasValue(defaultSettings)) { + continue; + } + if (settingsDef instanceof PointerTypeSettingsDefinition) { + ignoredSettings.add(AddressSpaceSettingsDefinition.class); + } + } + + for (SettingsDefinition settingsDef : typeDef.getSettingsDefinitions()) { + if (!(settingsDef instanceof TypeDefSettingsDefinition) || + !settingsDef.hasValue(defaultSettings)) { + continue; + } + boolean ignored = ignoredSettings.contains(settingsDef.getClass()); + if (buffy.length() == 0) { + buffy.append(INDENT_OPEN); + } + else { + buffy.append(BR); + } + buffy.append(TT_OPEN) + .append(settingsDef.getName()) + .append(": ") + .append(settingsDef.getValueString(defaultSettings)); + if (ignored) { + buffy.append(" (ignored)"); + } + buffy.append(TT_CLOSE); + } + if (buffy.length() != 0) { + buffy.append(INDENT_CLOSE); + lines.add(new TextLine(buffy.toString())); + } return lines; } @@ -168,6 +211,9 @@ public class TypeDefDataTypeHTMLRepresentation extends HTMLDataTypeRepresentatio // body buffy.append(BR); + if (typeDef.isPointer()) { + buffy.append("Pointer-"); + } buffy.append("TypeDef Base Data Type: ").append(BR); iterator = bodyLines.iterator(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java b/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java index bec59a8fdc..27d9fd100c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java @@ -1169,13 +1169,38 @@ public class CodeUnitFormat { } } + if (ref.isMemoryReference() && (ref instanceof OffsetReference)) { + return getOffsetReferenceRepresentation(cu, (OffsetReference) ref); + } + if (ref.isMemoryReference() || ref.isExternalReference()) { return getMemoryReferenceLabel(cu, ref); } + return null; } + private Object getOffsetReferenceRepresentation(CodeUnit cu, OffsetReference offsetRef) { + Reference baseRef = + new MemReferenceImpl(offsetRef.getFromAddress(), offsetRef.getBaseAddress(), + RefType.DATA, + offsetRef.getSource(), offsetRef.getOperandIndex(), offsetRef.isPrimary()); + Object baseRefObj = getMemoryReferenceLabel(cu, baseRef); + long offset = offsetRef.getOffset(); + String sign = "+"; + if (offset < 0) { + offset = -offset; + sign = "-"; + } + Scalar offsetScalar = new Scalar(64, offsetRef.getOffset(), true); + OperandRepresentationList list = new OperandRepresentationList(); + list.add(baseRefObj); + list.add(sign); + list.add(offsetScalar); + return list; + } + /** * Get a LabelString object which corresponds to the specified memory * reference from the specified code unit. Format options are considered diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/ByteCountSettingsDefinition.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/ByteCountSettingsDefinition.java index b9b5677104..537bc1e877 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/ByteCountSettingsDefinition.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/ByteCountSettingsDefinition.java @@ -37,12 +37,17 @@ public class ByteCountSettingsDefinition implements EnumSettingsDefinition { return DEFAULT; } Long value = settings.getLong(BYTE_COUNT); - if (value == null) { + if (value == null || value < 0 || value >= choices.length) { return DEFAULT; } return value.intValue(); } + @Override + public String getValueString(Settings settings) { + return choices[getChoice(settings)]; + } + @Override public void setChoice(Settings settings, int value) { if (value < DEFAULT) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/CodeUnitCountSettingsDefinition.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/CodeUnitCountSettingsDefinition.java index b6706b5264..53c2968ee3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/CodeUnitCountSettingsDefinition.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/CodeUnitCountSettingsDefinition.java @@ -52,12 +52,17 @@ public class CodeUnitCountSettingsDefinition implements EnumSettingsDefinition { return 0; } Long value = settings.getLong(CODE_UNIT_COUNT); - if (value == null) { + if (value == null || value < 0 || value >= choices.length) { return 0; } return value.intValue(); } + @Override + public String getValueString(Settings settings) { + return choices[getChoice(settings)]; + } + @Override public void setChoice(Settings settings, int value) { if (value < 0) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/CodeUnitOffsetSettingsDefinition.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/CodeUnitOffsetSettingsDefinition.java index 6c945fa62e..93996d63d0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/CodeUnitOffsetSettingsDefinition.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/CodeUnitOffsetSettingsDefinition.java @@ -56,12 +56,17 @@ public class CodeUnitOffsetSettingsDefinition implements EnumSettingsDefinition return DEFAULT_CHOICE; } Long value = settings.getLong(MEMORY_OFFSET); - if (value == null) { + if (value == null || value < 0 || value >= choices.length) { return DEFAULT_CHOICE; } return value.intValue(); } + @Override + public String getValueString(Settings settings) { + return choices[getChoice(settings)]; + } + @Override public void setChoice(Settings settings, int value) { if (value < 0) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionInlineSettingsDefinition.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionInlineSettingsDefinition.java index b3c884c8c5..9f9ae3f3ee 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionInlineSettingsDefinition.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionInlineSettingsDefinition.java @@ -26,7 +26,7 @@ public class FunctionInlineSettingsDefinition implements BooleanSettingsDefiniti private static final String INLINE = "Show inline"; private static final String NAME = INLINE; private static final String DESCRIPTION = - "On siganls to show the inline " + "function attribute when present"; + "On signals to show the inline " + "function attribute when present"; private static final boolean DEFAULT = false; @Override @@ -41,6 +41,11 @@ public class FunctionInlineSettingsDefinition implements BooleanSettingsDefiniti return Boolean.parseBoolean(value); } + @Override + public String getValueString(Settings settings) { + return Boolean.toString(getValue(settings)); + } + @Override public void setValue(Settings settings, boolean value) { settings.setString(INLINE, Boolean.toString(value)); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionNoReturnSettingsDefinition.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionNoReturnSettingsDefinition.java index 2755237ab9..1760178a24 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionNoReturnSettingsDefinition.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionNoReturnSettingsDefinition.java @@ -41,6 +41,11 @@ public class FunctionNoReturnSettingsDefinition implements BooleanSettingsDefini return Boolean.parseBoolean(value); } + @Override + public String getValueString(Settings settings) { + return Boolean.toString(getValue(settings)); + } + @Override public void setValue(Settings settings, boolean value) { settings.setString(NORETURN, Boolean.toString(value)); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionThunkSettingsDefinition.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionThunkSettingsDefinition.java index d24c9bf11d..138473874d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionThunkSettingsDefinition.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/FunctionThunkSettingsDefinition.java @@ -40,6 +40,11 @@ public class FunctionThunkSettingsDefinition implements BooleanSettingsDefinitio return Boolean.parseBoolean(value); } + @Override + public String getValueString(Settings settings) { + return Boolean.toString(getValue(settings)); + } + @Override public void setValue(Settings settings, boolean value) { settings.setString(THUNK, Boolean.toString(value)); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/MemoryOffsetSettingsDefinition.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/MemoryOffsetSettingsDefinition.java index dd565f4982..046c589c45 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/MemoryOffsetSettingsDefinition.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/field/MemoryOffsetSettingsDefinition.java @@ -55,12 +55,17 @@ public class MemoryOffsetSettingsDefinition implements EnumSettingsDefinition { return DEFAULT_CHOICE; } Long value = settings.getLong(MEMORY_OFFSET); - if (value == null) { + if (value == null || value < 0 || value >= choices.length) { return DEFAULT_CHOICE; } return value.intValue(); } + @Override + public String getValueString(Settings settings) { + return choices[getChoice(settings)]; + } + @Override public void setChoice(Settings settings, int value) { if (value < 0) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge5Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge5Test.java index 47e8bdc404..a28f076ec9 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge5Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/merge/datatypes/DataTypeMerge5Test.java @@ -22,13 +22,13 @@ import java.util.ArrayList; import org.junit.Assert; import org.junit.Test; -import ghidra.program.database.ProgramDB; -import ghidra.program.database.ProgramModifierListener; +import ghidra.docking.settings.Settings; +import ghidra.program.database.*; import ghidra.program.model.data.*; import ghidra.program.model.data.Enum; import ghidra.util.InvalidNameException; import ghidra.util.exception.DuplicateNameException; -import ghidra.util.task.TaskMonitorAdapter; +import ghidra.util.task.TaskMonitor; /** * @@ -53,10 +53,10 @@ public class DataTypeMerge5Test extends AbstractDataTypeMergeTest { try { Structure s = (Structure) dtm.getDataType(CategoryPath.ROOT, "DLL_Table"); - dtm.remove(s, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(s, TaskMonitor.DUMMY); DataType dt = dtm.getDataType(new CategoryPath("/Category1/Category2"), "CoolUnion"); - dtm.remove(dt, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(dt, TaskMonitor.DUMMY); commit = true; } finally { @@ -152,10 +152,10 @@ public class DataTypeMerge5Test extends AbstractDataTypeMergeTest { try { Structure s = (Structure) dtm.getDataType(CategoryPath.ROOT, "DLL_Table"); - dtm.remove(s, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(s, TaskMonitor.DUMMY); DataType dt = dtm.getDataType(new CategoryPath("/Category1/Category2"), "CoolUnion"); - dtm.remove(dt, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(dt, TaskMonitor.DUMMY); commit = true; } finally { @@ -273,10 +273,10 @@ public class DataTypeMerge5Test extends AbstractDataTypeMergeTest { try { Structure s = (Structure) dtm.getDataType(CategoryPath.ROOT, "DLL_Table"); - dtm.remove(s, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(s, TaskMonitor.DUMMY); DataType dt = dtm.getDataType(new CategoryPath("/Category1/Category2"), "CoolUnion"); - dtm.remove(dt, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(dt, TaskMonitor.DUMMY); commit = true; } finally { @@ -403,10 +403,10 @@ public class DataTypeMerge5Test extends AbstractDataTypeMergeTest { try { Structure s = (Structure) dtm.getDataType(CategoryPath.ROOT, "DLL_Table"); - dtm.remove(s, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(s, TaskMonitor.DUMMY); DataType dt = dtm.getDataType(new CategoryPath("/Category1/Category2"), "CoolUnion"); - dtm.remove(dt, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(dt, TaskMonitor.DUMMY); // edit FavoriteColors Enum enumm = @@ -546,15 +546,15 @@ public class DataTypeMerge5Test extends AbstractDataTypeMergeTest { try { Structure s = (Structure) dtm.getDataType(CategoryPath.ROOT, "DLL_Table"); - dtm.remove(s, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(s, TaskMonitor.DUMMY); DataType dt = dtm.getDataType(new CategoryPath("/Category1/Category2"), "CoolUnion"); - dtm.remove(dt, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(dt, TaskMonitor.DUMMY); // delete FavoriteColors Enum enumm = (Enum) dtm.getDataType(new CategoryPath("/MISC"), "FavoriteColors"); - dtm.remove(enumm, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(enumm, TaskMonitor.DUMMY); commit = true; } finally { @@ -687,15 +687,15 @@ public class DataTypeMerge5Test extends AbstractDataTypeMergeTest { try { Structure s = (Structure) dtm.getDataType(CategoryPath.ROOT, "DLL_Table"); - dtm.remove(s, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(s, TaskMonitor.DUMMY); DataType dt = dtm.getDataType(new CategoryPath("/Category1/Category2"), "CoolUnion"); - dtm.remove(dt, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(dt, TaskMonitor.DUMMY); // delete FavoriteColors Enum enumm = (Enum) dtm.getDataType(new CategoryPath("/MISC"), "FavoriteColors"); - dtm.remove(enumm, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(enumm, TaskMonitor.DUMMY); commit = true; } finally { @@ -816,10 +816,10 @@ public class DataTypeMerge5Test extends AbstractDataTypeMergeTest { try { Structure s = (Structure) dtm.getDataType(CategoryPath.ROOT, "DLL_Table"); - dtm.remove(s, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(s, TaskMonitor.DUMMY); DataType dt = dtm.getDataType(new CategoryPath("/Category1/Category2"), "CoolUnion"); - dtm.remove(dt, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(dt, TaskMonitor.DUMMY); // edit FavoriteColors Enum enumm = @@ -961,10 +961,10 @@ public class DataTypeMerge5Test extends AbstractDataTypeMergeTest { try { Structure s = (Structure) dtm.getDataType(CategoryPath.ROOT, "DLL_Table"); - dtm.remove(s, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(s, TaskMonitor.DUMMY); DataType dt = dtm.getDataType(new CategoryPath("/Category1/Category2"), "CoolUnion"); - dtm.remove(dt, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(dt, TaskMonitor.DUMMY); // edit FavoriteColors Enum enumm = @@ -1116,10 +1116,10 @@ public class DataTypeMerge5Test extends AbstractDataTypeMergeTest { try { Structure s = (Structure) dtm.getDataType(CategoryPath.ROOT, "DLL_Table"); - dtm.remove(s, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(s, TaskMonitor.DUMMY); DataType dt = dtm.getDataType(new CategoryPath("/Category1/Category2"), "CoolUnion"); - dtm.remove(dt, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(dt, TaskMonitor.DUMMY); // edit FavoriteColors Enum enumm = @@ -1412,6 +1412,109 @@ public class DataTypeMerge5Test extends AbstractDataTypeMergeTest { checkConflictCount(0); } + @Test + public void testTypeDefs10() throws Exception { + + // Exercise pointer-typedef setting changes + + mtf.initialize("notepad2", new OriginalProgramModifierListener() { + + @Override + public void modifyOriginal(ProgramDB program) throws Exception { + boolean commit = false; + DataTypeManager dtm = program.getDataTypeManager(); + int transactionID = program.startTransaction("test"); + try { + // must specify datatype manager when constructing to allow for settings to be made + Pointer p = dtm.getPointer(CharDataType.dataType); + TypeDef td = + new TypedefDataType(new CategoryPath("/MISC"), "PtrTypeDef", p, dtm); + + // NOTE: these are not viable settings but are intended to exercise all of them + Settings settings = td.getDefaultSettings(); + PointerTypeSettingsDefinition.DEF.setType(settings, + PointerType.IBO); + AddressSpaceSettingsDefinition.DEF.setValue(settings, "ROM"); + ComponentOffsetSettingsDefinition.DEF.setValue(settings, 0x10); + OffsetMaskSettingsDefinition.DEF.setValue(settings, 0x1234); + OffsetShiftSettingsDefinition.DEF.setValue(settings, 2); + + dtm.resolve(td, DataTypeConflictHandler.DEFAULT_HANDLER); + commit = true; + } + finally { + program.endTransaction(transactionID, commit); + } + } + + @Override + public void modifyLatest(ProgramDB program) { + boolean commit = false; + DataTypeManager dtm = program.getDataTypeManager(); + int transactionID = program.startTransaction("test"); + try { + TypeDef td = (TypeDef) dtm.getDataType(new CategoryPath("/MISC"), "PtrTypeDef"); + + Settings settings = td.getDefaultSettings(); + PointerTypeSettingsDefinition.DEF.setType(settings, + PointerType.RELATIVE); + AddressSpaceSettingsDefinition.DEF.clear(settings); + OffsetMaskSettingsDefinition.DEF.clear(settings); + OffsetShiftSettingsDefinition.DEF.setValue(settings, 3); + + commit = true; + } + finally { + program.endTransaction(transactionID, commit); + } + } + + @Override + public void modifyPrivate(ProgramDB program) { + boolean commit = false; + DataTypeManager dtm = program.getDataTypeManager(); + int transactionID = program.startTransaction("test"); + try { + TypeDef td = (TypeDef) dtm.getDataType(new CategoryPath("/MISC"), "PtrTypeDef"); + + Settings settings = td.getDefaultSettings(); + PointerTypeSettingsDefinition.DEF.clear(settings); + AddressSpaceSettingsDefinition.DEF.clear(settings); + OffsetMaskSettingsDefinition.DEF.setValue(settings, 0x5678); + OffsetShiftSettingsDefinition.DEF.setValue(settings, 4); + + commit = true; + } + finally { + program.endTransaction(transactionID, commit); + } + } + }); + executeMerge(); + + chooseOption(DataTypeMergeManager.OPTION_MY); + + waitForCompletion(); + + DataTypeManager dtm = resultProgram.getDataTypeManager(); + TypeDef td = (TypeDef) dtm.getDataType(new CategoryPath("/MISC"), "PtrTypeDef"); + + Settings settings = td.getDefaultSettings(); + assertFalse( + "Unexpected Pointer Type setting: " + + PointerTypeSettingsDefinition.DEF.getValueString(settings), + PointerTypeSettingsDefinition.DEF.hasValue(settings)); + assertFalse( + "Unexpected Address Space setting: " + + AddressSpaceSettingsDefinition.DEF.getValueString(settings), + AddressSpaceSettingsDefinition.DEF.hasValue(settings)); + assertEquals(0x10, ComponentOffsetSettingsDefinition.DEF.getValue(settings)); + assertEquals(0x5678, OffsetMaskSettingsDefinition.DEF.getValue(settings)); + assertEquals(4, OffsetShiftSettingsDefinition.DEF.getValue(settings)); + + checkConflictCount(0); + } + @Test public void testArrays() throws Exception { @@ -1427,10 +1530,10 @@ public class DataTypeMerge5Test extends AbstractDataTypeMergeTest { try { Structure s = (Structure) dtm.getDataType(CategoryPath.ROOT, "DLL_Table"); - dtm.remove(s, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(s, TaskMonitor.DUMMY); DataType dt = dtm.getDataType(new CategoryPath("/Category1/Category2"), "CoolUnion"); - dtm.remove(dt, TaskMonitorAdapter.DUMMY_MONITOR); + dtm.remove(dt, TaskMonitor.DUMMY); // edit FavoriteColors Enum enumm = diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorAlignmentTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorAlignmentTest.java index b81a6df05c..0016076cb8 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorAlignmentTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorAlignmentTest.java @@ -17,6 +17,8 @@ package ghidra.app.plugin.core.compositeeditor; import static org.junit.Assert.*; +import java.util.Arrays; + import javax.swing.JRadioButton; import javax.swing.JTextField; @@ -51,20 +53,23 @@ public class StructureEditorAlignmentTest extends AbstractStructureEditorTest { assertEquals(1, structureModel.getNumSelectedRows()); checkSelection(new int[] { 0 }); -// // Check enablement. -// CompositeEditorAction[] pActions = provider.getActions(); -// for (int i = 0; i < pActions.length; i++) { -// if ((pActions[i] instanceof FavoritesAction) -// || (pActions[i] instanceof CycleGroupAction) -// || (pActions[i] instanceof EditFieldAction) -// || (pActions[i] instanceof PointerAction) -// || (pActions[i] instanceof HexNumbersAction)) { -// checkEnablement(pActions[i], true); -// } -// else { -// checkEnablement(pActions[i], false); -// } -// } + // Check enablement for empty table with modified state. + CompositeEditorTableAction[] pActions = provider.getActions(); + for (int i = 0; i < pActions.length; i++) { + if ((pActions[i] instanceof FavoritesAction) || + (pActions[i] instanceof CycleGroupAction) || + (pActions[i] instanceof EditFieldAction) || + (pActions[i] instanceof PointerAction) || + (pActions[i] instanceof HexNumbersAction) || + (pActions[i] instanceof InsertUndefinedAction) || + (pActions[i] instanceof AddBitFieldAction) || + (pActions[i] instanceof ApplyAction)) { + checkEnablement(pActions[i], true); + } + else { + checkEnablement(pActions[i], false); + } + } DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); addDataType(new ByteDataType()); @@ -91,6 +96,8 @@ public class StructureEditorAlignmentTest extends AbstractStructureEditorTest { waitForSwing(); + assertTrue(Arrays.equals(new int[] { 0 }, model.getSelectedRows())); + assertEquals(3, structureModel.getNumComponents()); assertEquals(4, structureModel.getRowCount()); checkRow(0, 0, 1, "db", new ByteDataType(), "", ""); @@ -122,6 +129,13 @@ public class StructureEditorAlignmentTest extends AbstractStructureEditorTest { addDataType(new FloatDataType()); addDataType(arrayDt); + waitForSwing(); + + assertTrue(Arrays.equals(new int[] { 0 }, model.getSelectedRows())); + + // NOTE: Settings action is missing since it is provided by the DataPlugin + // which has not been added to the test tool + // Check enablement. CompositeEditorTableAction[] pActions = provider.getActions(); for (int i = 0; i < pActions.length; i++) { @@ -130,7 +144,15 @@ public class StructureEditorAlignmentTest extends AbstractStructureEditorTest { (pActions[i] instanceof EditFieldAction) || (pActions[i] instanceof InsertUndefinedAction) || (pActions[i] instanceof PointerAction) || - (pActions[i] instanceof HexNumbersAction) || (actions[i] instanceof ApplyAction)) { + (pActions[i] instanceof HexNumbersAction) || + (pActions[i] instanceof MoveDownAction) || + (pActions[i] instanceof DuplicateAction) || + (pActions[i] instanceof DuplicateMultipleAction) || + (pActions[i] instanceof DeleteAction) || + (pActions[i] instanceof ArrayAction) || + (pActions[i] instanceof CreateInternalStructureAction) || + (pActions[i] instanceof ShowComponentPathAction) || + (pActions[i] instanceof ApplyAction)) { checkEnablement(pActions[i], true); } else { @@ -158,6 +180,8 @@ public class StructureEditorAlignmentTest extends AbstractStructureEditorTest { waitForSwing(); + assertTrue(Arrays.equals(new int[] { 0 }, model.getSelectedRows())); + pressButtonByName(getPanel(), "Packing Enablement"); // toggle -> enable packing assertIsPackingEnabled(true); assertDefaultPacked(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorAlignmentTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorAlignmentTest.java index db815aad5f..dc730d5b72 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorAlignmentTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/UnionEditorAlignmentTest.java @@ -47,20 +47,21 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { assertEquals(1, unionModel.getNumSelectedRows()); checkSelection(new int[] { 0 }); -// // Check enablement. -// CompositeEditorAction[] pActions = provider.getActions(); -// for (int i = 0; i < pActions.length; i++) { -// if ((pActions[i] instanceof FavoritesAction) -// || (pActions[i] instanceof CycleGroupAction) -// || (pActions[i] instanceof EditFieldAction) -// || (pActions[i] instanceof PointerAction) -// || (pActions[i] instanceof HexNumbersAction)) { -// checkEnablement(pActions[i], true); -// } -// else { -// checkEnablement(pActions[i], false); -// } -// } + // Check enablement for empty table with modified state. + CompositeEditorTableAction[] pActions = provider.getActions(); + for (int i = 0; i < pActions.length; i++) { + if ((pActions[i] instanceof FavoritesAction) || + (pActions[i] instanceof CycleGroupAction) || + (pActions[i] instanceof EditFieldAction) || + (pActions[i] instanceof PointerAction) || + (pActions[i] instanceof HexNumbersAction) || + (pActions[i] instanceof ApplyAction)) { + checkEnablement(pActions[i], true); + } + else { + checkEnablement(pActions[i], false); + } + } DataType arrayDt = new ArrayDataType(new CharDataType(), 5, 1); addDataType(new ByteDataType()); @@ -111,16 +112,23 @@ public class UnionEditorAlignmentTest extends AbstractUnionEditorTest { // Check enablement. CompositeEditorTableAction[] pActions = provider.getActions(); - for (CompositeEditorTableAction pAction : pActions) { - if ((pAction instanceof FavoritesAction) || - (pAction instanceof CycleGroupAction) || - (pAction instanceof EditFieldAction) || - (pAction instanceof PointerAction) || - (pAction instanceof HexNumbersAction) || (pAction instanceof ApplyAction)) { - checkEnablement(pAction, true); + for (int i = 0; i < pActions.length; i++) { + if ((pActions[i] instanceof FavoritesAction) || + (pActions[i] instanceof CycleGroupAction) || + (pActions[i] instanceof EditFieldAction) || + (pActions[i] instanceof PointerAction) || + (pActions[i] instanceof HexNumbersAction) || + (pActions[i] instanceof MoveDownAction) || + (pActions[i] instanceof DuplicateAction) || + (pActions[i] instanceof DuplicateMultipleAction) || + (pActions[i] instanceof DeleteAction) || + (pActions[i] instanceof ArrayAction) || + (pActions[i] instanceof ShowComponentPathAction) || + (pActions[i] instanceof ApplyAction)) { + checkEnablement(pActions[i], true); } else { - checkEnablement(pAction, false); + checkEnablement(pActions[i], false); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/AbstractDataActionTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/AbstractDataActionTest.java index 49de12948f..2b945d0dcb 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/AbstractDataActionTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/AbstractDataActionTest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.*; import java.math.BigInteger; import java.util.*; +import java.util.stream.Collectors; import javax.swing.event.ChangeEvent; import javax.swing.table.TableCellEditor; @@ -38,8 +39,8 @@ import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.plugin.core.clear.ClearCmd; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; -import ghidra.app.plugin.core.data.DataSettingsDialog.SettingsEditor; -import ghidra.app.plugin.core.data.DataSettingsDialog.SettingsRowObject; +import ghidra.app.plugin.core.data.AbstractSettingsDialog.SettingsEditor; +import ghidra.app.plugin.core.data.AbstractSettingsDialog.SettingsRowObject; import ghidra.app.plugin.core.navigation.NextPrevAddressPlugin; import ghidra.docking.settings.FormatSettingsDefinition; import ghidra.docking.settings.SettingsDefinition; @@ -79,7 +80,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra protected static final String CREATE_STRUCTURE = "Create Structure"; protected static final String EDIT_DATA_TYPE = "Edit Data Type"; protected static final String CREATE_ARRAY = "Define Array"; - protected static final String DEFAULT_DATA_SETTINGS = "Default Data Settings"; + protected static final String DEFAULT_SETTINGS = "Default Settings"; protected static final String DATA_SETTINGS = "Data Settings"; protected static final String CHOOSE_DATA_TYPE = "Choose Data Type"; @@ -178,7 +179,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra checkAction(actions, EDIT_DATA_TYPE, enabled, caseStr); checkAction(actions, CREATE_ARRAY, enabled, caseStr); checkAction(actions, CHOOSE_DATA_TYPE, enabled, caseStr); - checkAction(actions, DEFAULT_DATA_SETTINGS, enabled, caseStr); + checkAction(actions, DEFAULT_SETTINGS, enabled, caseStr); checkAction(actions, DATA_SETTINGS, enabled, caseStr); checkAction(actions, CYCLE_FLOAT_DOUBLE, enabled, caseStr); checkAction(actions, CYCLE_BYTE_WORD_DWORD_QWORD, enabled, caseStr); @@ -220,7 +221,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra waitForSwing(); - final DataSettingsDialog dlg = waitForDialogComponent(DataSettingsDialog.class); + final AbstractSettingsDialog dlg = waitForDialogComponent(AbstractSettingsDialog.class); assertNotNull("Expected data settings dialog", dlg); waitForSwing(); @@ -244,11 +245,11 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra protected void changeSettings(final boolean defaultSetting, final String[] settingNames, final String[] newValues) throws Exception { - doAction(defaultSetting ? DEFAULT_DATA_SETTINGS : DATA_SETTINGS, false); + doAction(defaultSetting ? DEFAULT_SETTINGS : DATA_SETTINGS, false); waitForSwing(); - final DataSettingsDialog dlg = waitForDialogComponent(DataSettingsDialog.class); + final AbstractSettingsDialog dlg = waitForDialogComponent(AbstractSettingsDialog.class); assertNotNull("Expected data settings dialog", dlg); waitForSwing(); @@ -296,12 +297,12 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra waitForSwing(); } - private void endEdit(DataSettingsDialog d) { + private void endEdit(AbstractSettingsDialog d) { GTable table = d.getSettingsTable(); runSwing(() -> table.editingStopped(new ChangeEvent(table))); } - private void setComboValue(DataSettingsDialog d, String string) { + private void setComboValue(AbstractSettingsDialog d, String string) { GTable table = d.getSettingsTable(); TableCellEditor activeEditor = runSwing(() -> table.getCellEditor()); assertNotNull("Table should be editing, but is not", activeEditor); @@ -328,7 +329,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra }); } - private void triggerEdit(DataSettingsDialog d, int row, int col) { + private void triggerEdit(AbstractSettingsDialog d, int row, int col) { GTable table = d.getSettingsTable(); boolean editStarted = runSwing(() -> table.editCellAt(row, col)); assertTrue("Unable to edit dialog table cell at " + row + ", " + col, editStarted); @@ -359,19 +360,18 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra protected boolean firstIteration = true; protected void manipulateAllSettings(boolean testDefaultSetting, boolean insideStruct, - boolean commonStruct, String defineAction) throws Exception { + boolean commonStruct, DockingActionIf dockingAction) throws Exception { long loc1 = 0x1006a02; long loc2 = 0x100abeb; - DockingActionIf dockingAction = getAction(defineAction); assertNotNull(dockingAction); DataType dt; boolean useSelection = true; if (dockingAction instanceof DataAction) { - DataAction action = (DataAction) getAction(defineAction); + DataAction action = (DataAction) dockingAction; dt = action.getDataType(); useSelection = (dt instanceof StringDataType); } @@ -404,7 +404,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra if (useSelection) { makeSelection(loc1, loc1 + 0x10); } - doAction(defineAction, true); + doAction(dockingAction, true); Data data1 = getContextData(); if (insideStruct) { @@ -435,7 +435,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra if (useSelection) { makeSelection(loc2, loc2 + 0x10); } - doAction(defineAction, true); + doAction(dockingAction, true); data2 = getContextData(); if (insideStruct) { @@ -485,9 +485,9 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra manipulateTerminatedSettings(testDefaultSetting, insideStruct, commonStruct, data1, data2); } - else if (sdef instanceof DataTypeMnemonicSettingsDefinition) { - // TODO: ??? + manipulateMnemonicSettings(testDefaultSetting, insideStruct, commonStruct, + data1, data2); } else if (sdef instanceof MutabilitySettingsDefinition) { manipulateMutabilitySettings(testDefaultSetting, insideStruct, commonStruct, @@ -864,6 +864,62 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra } + /** + * Test MNEMONIC data setting + * @param testDefaultSetting if true test default setting, else test instance setting for data2 + * @param insideStruct data are inside two structure instances + * @param commonStruct data structures are the same type + * @param data1 data at some location with same type as data2 + * @param data2 data at current location + * @throws Exception + */ + protected void manipulateMnemonicSettings(boolean testDefaultSetting, boolean insideStruct, + boolean commonStruct, Data data1, Data data2) throws Exception { + + assertSame(data1.getDataType(), data2.getDataType()); + + boolean settingsAreShared = + testDefaultSetting && (!insideStruct || (insideStruct && commonStruct)); + + String[] settingNames = new String[] { "Mnemonic-style" }; + + useDefaultSettings(); + + DataType dt = data1.getDataType(); + if (dt instanceof Array) { + dt = ((Array) dt).getDataType(); + data1 = data1.getComponent(0); + data2 = data2.getComponent(0); + } + + // Currently only supported by integer types + assertTrue(dt instanceof AbstractIntegerDataType); + + AbstractIntegerDataType intDt = (AbstractIntegerDataType) dt; + String defaultMnemonic = intDt.getDisplayName(); + String assemblyMnemonic = intDt.getAssemblyMnemonic(); // this is the real default setting + String cMnemonic = intDt.getCMnemonic(); + + assertEquals(assemblyMnemonic, data1.getMnemonicString()); + assertEquals(assemblyMnemonic, data2.getMnemonicString()); + + changeSettings(testDefaultSetting, settingNames, new String[] { "C" }); + assertEquals(settingsAreShared ? cMnemonic : assemblyMnemonic, data1.getMnemonicString()); + assertEquals(cMnemonic, data2.getMnemonicString()); + + changeSettings(testDefaultSetting, settingNames, new String[] { "assembly" }); + + assertEquals(assemblyMnemonic, data1.getMnemonicString()); + assertEquals(assemblyMnemonic, data2.getMnemonicString()); + + changeSettings(testDefaultSetting, settingNames, new String[] { "default" }); + + assertEquals(settingsAreShared ? defaultMnemonic : assemblyMnemonic, + data1.getMnemonicString()); + assertEquals(defaultMnemonic, data2.getMnemonicString()); + + } + protected String getDataTypeAction(String dtName) { String actionName = "Define " + dtName; if (getAction(actionName) == null) { @@ -1114,7 +1170,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra ProgramSelection sel = getCurrentSelection(); boolean useSelection = (sel != null && !sel.isEmpty()); - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); for (DockingActionIf element : actions) { MenuData menuBarData = element.getMenuBarData(); @@ -1189,7 +1245,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra checkAction(actions, CREATE_STRUCTURE, false, caseName); checkAction(actions, EDIT_DATA_TYPE, editStructOK, caseName); checkAction(actions, CREATE_ARRAY, false, caseName); - checkAction(actions, DEFAULT_DATA_SETTINGS, hasSettings, caseName); + checkAction(actions, DEFAULT_SETTINGS, hasSettings, caseName); checkAction(actions, DATA_SETTINGS, hasSettings, caseName); checkAction(actions, CYCLE_FLOAT_DOUBLE, false, caseName); checkAction(actions, CYCLE_BYTE_WORD_DWORD_QWORD, false, caseName); @@ -1213,7 +1269,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra protected void checkOnUndefined(Set actions) { if (actions == null) { - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); } Data data = getContextData(); @@ -1233,7 +1289,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra checkAction(actions, EDIT_DATA_TYPE, pdata != null && (pdata.isStructure() || pdata.isUnion()), caseName); checkAction(actions, CREATE_ARRAY, true, caseName); - checkAction(actions, DEFAULT_DATA_SETTINGS, false, caseName); + checkAction(actions, DEFAULT_SETTINGS, false, caseName); checkAction(actions, DATA_SETTINGS, hasNormalUnitSelection, caseName); checkAction(actions, CYCLE_FLOAT_DOUBLE, true, caseName); checkAction(actions, CYCLE_BYTE_WORD_DWORD_QWORD, true, caseName); @@ -1252,7 +1308,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra protected void checkOnDefined(Set actions, Class expectedDataType) { if (actions == null) { - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); } String dtName = expectedDataType.getName(); @@ -1300,7 +1356,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra (pdata != null && (pdata.isStructure() || pdata.isUnion())) || (dt instanceof Enum), caseName); checkAction(actions, CREATE_ARRAY, true, caseName); - checkAction(actions, DEFAULT_DATA_SETTINGS, + checkAction(actions, DEFAULT_SETTINGS, (!hasSelection || isSelectionJustSingleDataInstance(sel, d)) && hasSettings, caseName); checkAction(actions, DATA_SETTINGS, hasNormalUnitSelection || hasSettings, caseName); checkAction(actions, CYCLE_FLOAT_DOUBLE, onFloatDoubleData, caseName); @@ -1319,7 +1375,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra protected void checkOnArray(Set actions, DataType interiorDt, int arraySize) { if (actions == null) { - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); } Data d = getContextData(); @@ -1363,7 +1419,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra checkAction(actions, EDIT_DATA_TYPE, pdata != null && (pdata.isStructure() || pdata.isUnion()), caseName); checkAction(actions, CREATE_ARRAY, true, caseName); - checkAction(actions, DEFAULT_DATA_SETTINGS, + checkAction(actions, DEFAULT_SETTINGS, hasSettings && (!hasSelection || isSelectionJustSingleDataInstance(sel, d)), caseName); checkAction(actions, DATA_SETTINGS, hasSettings, caseName); checkAction(actions, CYCLE_FLOAT_DOUBLE, true, caseName); @@ -1388,7 +1444,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra protected void checkOnStructure(Set actions, int structSize) { if (actions == null) { - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); } Data d = getContextData(); @@ -1412,7 +1468,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra checkAction(actions, CREATE_STRUCTURE, sel != null && !sel.isEmpty(), caseName); checkAction(actions, EDIT_DATA_TYPE, true, caseName); checkAction(actions, CREATE_ARRAY, true, caseName); - checkAction(actions, DEFAULT_DATA_SETTINGS, false, caseName); + checkAction(actions, DEFAULT_SETTINGS, false, caseName); checkAction(actions, DATA_SETTINGS, hasNormalUnitSelection, caseName); checkAction(actions, CYCLE_FLOAT_DOUBLE, true, caseName); checkAction(actions, CYCLE_BYTE_WORD_DWORD_QWORD, true, caseName); @@ -1428,8 +1484,24 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra } + /** + * Get the specified DataPlugin action (without context constraint) + * @param name action name + * @return action or null if not found + */ protected DockingActionIf getAction(String name) { - Set actions = getActionsByOwner(tool, plugin.getName()); + return getAction(name, null); + } + + /** + * Get the specified DataPlugin action. + * @param name action name + * @param context if not null will only return an action which is + * valid for this context. + * @return action or null if not found + */ + protected DockingActionIf getAction(String name, ActionContext context) { + Set actions = getDataPluginActions(context); for (DockingActionIf element : actions) { String actionName = element.getName(); int pos = actionName.indexOf(" ("); @@ -1443,11 +1515,54 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra return null; } + /** + * Get the set of all actions owned by DataPlugin which are valid for + * the specified context. + * @param context current action context + * @return set of valid actions + */ + protected Set getDataPluginActions(ActionContext context) { + Set actions = getActionsByOwner(tool, plugin.getName()); + if (context == null) { + return actions; + } + // assumes returned set may be modified + return actions.stream() + .filter(a -> a.isValidContext(context)) + .collect(Collectors.toSet()); + } + + /** + * Get the set of all actions owned by DataPlugin which are valid for + * the current action context. + * @return set of valid actions + */ + protected Set getDataPluginActions() { + return getDataPluginActions(getProgramContext()); + } + + /** + * Execute the specified DataPlugin action using the current program context. + * Assertion failures will occur if action not found or is not enabled for the + * current context. + * @param name action name + * @param waitForCompletion if true invocation will wait for action to complete + * its execution in the swing thread, if false it will be scheduled and return immediately. + */ protected void doAction(String name, boolean waitForCompletion) { - DockingActionIf action = getAction(name); - assertNotNull("Action was not found: " + name, action); + ActionContext programContext = getProgramContext(); + DockingActionIf action = getAction(name, programContext); + String contextMsg = ""; + if (programContext != null) { + contextMsg = " (" + programContext.getClass().getSimpleName() + ")"; + } + assertNotNull("Action was not found" + contextMsg + ": " + name, action); + doAction(action, waitForCompletion); + } + + protected void doAction(DockingActionIf action, boolean waitForCompletion) { if (!action.isEnabledForContext(getProgramContext())) { - Assert.fail("Action is not valid: " + name); + Assert.fail("Action is not valid: " + action.getName()); } try { @@ -1455,7 +1570,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra } catch (Throwable t) { t.printStackTrace(); - Assert.fail("Action '" + name + "' failed: " + t.toString()); + Assert.fail("Action '" + action.getName() + "' failed: " + t.toString()); } } @@ -1498,6 +1613,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra boolean enabledForContext = action.isEnabledForContext(programContext); if (isValidContext != enabledForContext) { Msg.debug(this, "checkAction(): "); + action.isEnabledForContext(programContext); } assertEquals( "Context is not in correct valid state. Context: actionName = " + action.getName() + diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction1Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction1Test.java index 2d93d0fe40..b9ff2fabee 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction1Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction1Test.java @@ -20,6 +20,7 @@ import java.util.List; import org.junit.Test; import org.junit.experimental.categories.Category; +import docking.action.DockingActionIf; import generic.test.category.NightlyCategory; import ghidra.program.model.data.DataType; @@ -32,8 +33,9 @@ public class DataAction1Test extends AbstractDataActionTest { List builtIns = getBuiltInDataTypesAsFavorites(); for (DataType type : builtIns) { String actionName = "Define " + type.getName(); - manipulateAllSettings(false, true, false, actionName); - manipulateAllSettings(true, true, true, actionName); + DockingActionIf action = getAction(actionName); + manipulateAllSettings(false, true, false, action); + manipulateAllSettings(true, true, true, action); } } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction2Test.java index e7c37bb086..655d206123 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction2Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction2Test.java @@ -20,6 +20,7 @@ import java.util.List; import org.junit.Test; import org.junit.experimental.categories.Category; +import docking.action.DockingActionIf; import generic.test.category.NightlyCategory; import ghidra.program.model.data.DataType; @@ -32,8 +33,9 @@ public class DataAction2Test extends AbstractDataActionTest { List builtIns = getBuiltInDataTypesAsFavorites(); for (DataType type : builtIns) { String actionName = "Define " + type.getName(); - manipulateAllSettings(true, true, false, actionName); - manipulateAllSettings(true, true, true, actionName); + DockingActionIf action = getAction(actionName); + manipulateAllSettings(true, true, false, action); + manipulateAllSettings(true, true, true, action); } } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction3Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction3Test.java index 1284cc8049..3252bba67e 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction3Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction3Test.java @@ -32,7 +32,7 @@ public class DataAction3Test extends AbstractDataActionTest { List builtIns = getBuiltInDataTypesAsFavorites(); for (DataType type : builtIns) { String actionName = "Define " + type.getName(); - manipulateAllSettings(true, false, false, actionName); + manipulateAllSettings(true, false, false, getAction(actionName)); } } @@ -42,7 +42,7 @@ public class DataAction3Test extends AbstractDataActionTest { List builtIns = getBuiltInDataTypesAsFavorites(); for (DataType type : builtIns) { String actionName = "Define " + type.getName(); - manipulateAllSettings(false, false, false, actionName); + manipulateAllSettings(false, false, false, getAction(actionName)); } } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction4Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction4Test.java index 058a6264a9..4cb3d65e16 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction4Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/DataAction4Test.java @@ -45,9 +45,8 @@ public class DataAction4Test extends AbstractDataActionTest { try { closeProgram(); - actions = getActionsByOwner(tool, plugin.getName()); - assertEquals(ACTION_COUNT, actions.size()); - checkActions(actions, false, "Start"); + actions = getDataPluginActions(); + assertEquals(0, actions.size()); openProgram(); } @@ -60,9 +59,8 @@ public class DataAction4Test extends AbstractDataActionTest { closeProgram(); - actions = getActionsByOwner(tool, plugin.getName()); - assertEquals(ACTION_COUNT, actions.size()); - checkActions(actions, false, "Start"); + actions = getDataPluginActions(); + assertEquals(0, actions.size()); } @Test @@ -73,13 +71,13 @@ public class DataAction4Test extends AbstractDataActionTest { doAction(DEFINE_BYTE, true); - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); assertEquals(ACTION_COUNT, actions.size()); checkOnDefined(actions, ByteDataType.class); undo(program); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnUndefined(actions); gotoLocation(0x010069f2); @@ -107,7 +105,7 @@ public class DataAction4Test extends AbstractDataActionTest { doAction(DEFINE_WORD, true); - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); assertEquals(ACTION_COUNT, actions.size()); checkOnDefined(actions, WordDataType.class); @@ -138,7 +136,7 @@ public class DataAction4Test extends AbstractDataActionTest { doAction(DEFINE_DWORD, true); - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); assertEquals(ACTION_COUNT, actions.size()); checkOnDefined(actions, DWordDataType.class); @@ -169,7 +167,7 @@ public class DataAction4Test extends AbstractDataActionTest { doAction(DEFINE_QWORD, true); - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); assertEquals(ACTION_COUNT, actions.size()); checkOnDefined(actions, QWordDataType.class); @@ -200,7 +198,7 @@ public class DataAction4Test extends AbstractDataActionTest { doAction(DEFINE_FLOAT, true); - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); assertEquals(ACTION_COUNT, actions.size()); checkOnDefined(actions, FloatDataType.class); @@ -231,7 +229,7 @@ public class DataAction4Test extends AbstractDataActionTest { doAction(DEFINE_DOUBLE, true); - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); assertEquals(ACTION_COUNT, actions.size()); checkOnDefined(actions, DoubleDataType.class); @@ -264,7 +262,7 @@ public class DataAction4Test extends AbstractDataActionTest { doAction(CYCLE_CHAR_STRING_UNICODE, true); - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); assertEquals(ACTION_COUNT, actions.size()); checkOnDefined(actions, CharDataType.class); @@ -283,15 +281,15 @@ public class DataAction4Test extends AbstractDataActionTest { checkOnDefined(actions, CharDataType.class); doAction(CYCLE_CHAR_STRING_UNICODE, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, StringDataType.class); doAction(CYCLE_CHAR_STRING_UNICODE, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, UnicodeDataType.class); doAction(CYCLE_CHAR_STRING_UNICODE, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, CharDataType.class); clearLocation(0x01006a00); @@ -326,7 +324,7 @@ public class DataAction4Test extends AbstractDataActionTest { doAction(CYCLE_BYTE_WORD_DWORD_QWORD, true); - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); assertEquals(ACTION_COUNT, actions.size()); checkOnDefined(actions, ByteDataType.class); @@ -345,19 +343,19 @@ public class DataAction4Test extends AbstractDataActionTest { checkOnDefined(actions, ByteDataType.class); doAction(CYCLE_BYTE_WORD_DWORD_QWORD, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, WordDataType.class); doAction(CYCLE_BYTE_WORD_DWORD_QWORD, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, DWordDataType.class); doAction(CYCLE_BYTE_WORD_DWORD_QWORD, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, QWordDataType.class); doAction(CYCLE_BYTE_WORD_DWORD_QWORD, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, ByteDataType.class); clearLocation(0x01006a00); @@ -387,19 +385,19 @@ public class DataAction4Test extends AbstractDataActionTest { // Test cycle when it does not fit gotoLocation(0x010069f0); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnUndefined(actions); doAction(CYCLE_BYTE_WORD_DWORD_QWORD, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, ByteDataType.class); doAction(CYCLE_BYTE_WORD_DWORD_QWORD, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, WordDataType.class); doAction(CYCLE_BYTE_WORD_DWORD_QWORD, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnUndefined(actions); } @@ -414,7 +412,7 @@ public class DataAction4Test extends AbstractDataActionTest { doAction(CYCLE_FLOAT_DOUBLE, true); - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); assertEquals(ACTION_COUNT, actions.size()); checkOnDefined(actions, FloatDataType.class); @@ -433,11 +431,11 @@ public class DataAction4Test extends AbstractDataActionTest { checkOnDefined(actions, FloatDataType.class); doAction(CYCLE_FLOAT_DOUBLE, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, DoubleDataType.class); doAction(CYCLE_FLOAT_DOUBLE, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, FloatDataType.class); clearLocation(0x01006a00); @@ -461,15 +459,15 @@ public class DataAction4Test extends AbstractDataActionTest { // Test cycle when it does not fit gotoLocation(0x010069ee); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnUndefined(actions); doAction(CYCLE_FLOAT_DOUBLE, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, FloatDataType.class); doAction(CYCLE_FLOAT_DOUBLE, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnUndefined(actions); } @@ -495,7 +493,7 @@ public class DataAction4Test extends AbstractDataActionTest { waitForPostedSwingRunnables(); - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); checkOnArray(actions, null, 0x20); // Test action disablement on array element location @@ -524,7 +522,7 @@ public class DataAction4Test extends AbstractDataActionTest { waitForPostedSwingRunnables(); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnArray(actions, new ByteDataType(), 0x10); } @@ -541,7 +539,7 @@ public class DataAction4Test extends AbstractDataActionTest { clearSelection();// Remove selection to allow array check to work - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); checkOnArray(actions, null, 0x20); // Create Byte[0x10] array @@ -555,7 +553,7 @@ public class DataAction4Test extends AbstractDataActionTest { clearSelection();// Remove selection to allow array check to work - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnArray(actions, new ByteDataType(), 0x10); } @@ -588,7 +586,7 @@ public class DataAction4Test extends AbstractDataActionTest { clearSelection(); - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); checkOnStructure(actions, 0x20); gotoLocation(0x01006c00); @@ -705,14 +703,14 @@ public class DataAction4Test extends AbstractDataActionTest { doAction(DEFINE_BYTE, true); - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); checkOnDefined(actions, ByteDataType.class); gotoLocation(0x01006a01, new int[] { 1 }); doAction(DEFINE_FLOAT, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, FloatDataType.class); Data pdata = getContextData().getParent(); @@ -754,7 +752,7 @@ public class DataAction4Test extends AbstractDataActionTest { waitForPostedSwingRunnables(); - Set actions = getActionsByOwner(tool, plugin.getName()); + Set actions = getDataPluginActions(); checkOnArray(actions, structDt, 5); // Expand structure @@ -769,14 +767,14 @@ public class DataAction4Test extends AbstractDataActionTest { doAction(DEFINE_BYTE, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, ByteDataType.class); gotoLocation(0x01006a01, new int[] { 0, 1 }); doAction(DEFINE_FLOAT, true); - actions = getActionsByOwner(tool, plugin.getName()); + actions = getDataPluginActions(); checkOnDefined(actions, FloatDataType.class); Data pdata = getContextData().getParent(); @@ -946,7 +944,7 @@ public class DataAction4Test extends AbstractDataActionTest { gotoLocation(0x100abeb); doAction(DEFINE_BYTE, true); - manipulateAllSettings(false, false, false, CREATE_ARRAY); + manipulateAllSettings(false, false, false, getAction(CREATE_ARRAY)); } @Test @@ -960,7 +958,7 @@ public class DataAction4Test extends AbstractDataActionTest { gotoLocation(0x100abeb); doAction(DEFINE_BYTE, true); - manipulateAllSettings(false, true, false, CREATE_ARRAY); - manipulateAllSettings(true, true, true, CREATE_ARRAY); + manipulateAllSettings(false, true, false, getAction(CREATE_ARRAY)); + manipulateAllSettings(true, true, true, getAction(CREATE_ARRAY)); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EnumDataActionTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EnumDataActionTest.java index bcfa2db9bc..9e8636d9dd 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EnumDataActionTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EnumDataActionTest.java @@ -18,6 +18,7 @@ package ghidra.app.plugin.core.data; import org.junit.Before; import org.junit.Test; +import docking.action.DockingActionIf; import ghidra.program.model.data.EnumDataType; public class EnumDataActionTest extends AbstractDataActionTest { @@ -48,11 +49,11 @@ public class EnumDataActionTest extends AbstractDataActionTest { @Test public void testAllEnumDataSettings() throws Exception { - String actionName = enumDataAction.getName(); - manipulateAllSettings(false, true, false, actionName); - manipulateAllSettings(true, true, true, actionName); - manipulateAllSettings(false, false, false, actionName); - manipulateAllSettings(true, false, false, actionName); + DockingActionIf action = getAction(enumDataAction.getName()); + manipulateAllSettings(false, true, false, action); + manipulateAllSettings(true, true, true, action); + manipulateAllSettings(false, false, false, action); + manipulateAllSettings(true, false, false, action); } class TestEnumDataAction extends DataAction { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MoveBlockModelTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MoveBlockModelTest.java index 3b60e55f1c..f18dcd8a4f 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MoveBlockModelTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MoveBlockModelTest.java @@ -22,9 +22,9 @@ import org.junit.*; import ghidra.app.cmd.memory.MoveBlockListener; import ghidra.app.cmd.memory.MoveBlockTask; import ghidra.program.database.ProgramBuilder; -import ghidra.program.database.data.DataTypeManagerDB; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.data.ProgramBasedDataTypeManager; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryBlock; import ghidra.test.AbstractGhidraHeadedIntegrationTest; @@ -80,12 +80,11 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest model.initialize(block); int transactionID = x8051.startTransaction("Set settings"); - DataTypeManagerDB dtm = (DataTypeManagerDB) x8051.getDataTypeManager(); + ProgramBasedDataTypeManager dtm = x8051.getDataTypeManager(); for (int i = 0; i < 10; i++) { Address a = getAddr(x8051, "BITS", i); dtm.setStringSettingsValue(a, "color", "red" + i); dtm.setLongSettingsValue(a, "someLongValue", i); - dtm.setByteSettingsValue(a, "bytes", new byte[] { 0, 1, 2 }); } x8051.endTransaction(transactionID, true); } @@ -196,7 +195,7 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest waitForCondition(() -> moveCompleted && x8051.canLock()); // make sure settings on data got moved - DataTypeManagerDB dtm = (DataTypeManagerDB) x8051.getDataTypeManager(); + ProgramBasedDataTypeManager dtm = x8051.getDataTypeManager(); for (int i = 0; i < 10; i++) { Address a = getAddr(x8051, "CODE", 0x2000 + i); @@ -205,8 +204,6 @@ public class MoveBlockModelTest extends AbstractGhidraHeadedIntegrationTest Long lvalue = dtm.getLongSettingsValue(a, "someLongValue"); assertEquals(i, lvalue.longValue()); - - assertNotNull(dtm.getByteSettingsValue(a, "bytes")); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/SettingsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/SettingsTest.java index 077e976455..87545151e9 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/SettingsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/SettingsTest.java @@ -32,7 +32,7 @@ import ghidra.program.model.listing.Listing; import ghidra.program.model.mem.Memory; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.util.exception.CancelledException; -import ghidra.util.task.TaskMonitorAdapter; +import ghidra.util.task.TaskMonitor; /** * @@ -42,11 +42,15 @@ import ghidra.util.task.TaskMonitorAdapter; */ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { private ProgramDB program; - private DataTypeManagerDB dataMgr; + private ProgramBasedDataTypeManager dataMgr; private Listing listing; private AddressSpace space; private int transactionID; + // NOTE: Datatypes must be resolved before settings may be changed + // with the exception of TypeDefDataType which does permit + // TypeDefSettingsDefinition settings defined by the base-datatype. + public SettingsTest() { super(); } @@ -71,49 +75,62 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testSetDefaultSettings() throws Exception { - listing.createData(addr(10), new ByteDataType(), 1); - Data data = listing.getDataAt(addr(10)); - ByteDataType dt = (ByteDataType) data.getDataType(); + + DataType dt = ByteDataType.dataType; + Settings defaultSettings = dt.getDefaultSettings(); defaultSettings.setString("color", "red"); defaultSettings.setLong("someLongValue", 10); - defaultSettings.setByteArray("bytes", new byte[] { 0, 1, 2 }); + + assertNull(defaultSettings.getString("color")); + assertNull(defaultSettings.getLong("someLongValue")); + + // May modify byte default settings after resolve + dt = dataMgr.resolve(dt, null); + + defaultSettings = dt.getDefaultSettings(); + defaultSettings.setString("color", "red"); + defaultSettings.setLong("someLongValue", 10); assertEquals("red", defaultSettings.getString("color")); Long lv = defaultSettings.getLong("someLongValue"); assertNotNull(lv); assertEquals(10, lv.longValue()); - byte[] b = defaultSettings.getByteArray("bytes"); - assertNotNull(b); - assertEquals(3, b.length); - defaultSettings.setValue("long", new Long(10)); + defaultSettings.setValue("long", 10L); Object obj = defaultSettings.getValue("long"); assertNotNull(obj); assertEquals(10, ((Long) obj).longValue()); } @Test - public void testSetDefaultSettings2() throws Exception { - TypedefDataType td = new TypedefDataType("ByteTypedef", new ByteDataType()); - listing.createData(addr(10), td, td.getLength()); - Data data = listing.getDataAt(addr(10)); - TypeDef typeDef = (TypeDef) data.getDataType(); + public void testSetTypedefDefaultSettings() throws Exception { + + TypeDef typeDef = new TypedefDataType(CategoryPath.ROOT, "ByteTypedef", + new ByteDataType(dataMgr), dataMgr); + + assertEquals(0, ByteDataType.dataType.getTypeDefSettingsDefinitions().length); Settings defaultSettings = typeDef.getDefaultSettings(); defaultSettings.setString("color", "red"); defaultSettings.setLong("someLongValue", 10); - defaultSettings.setByteArray("bytes", new byte[] { 0, 1, 2 }); + + assertNull(defaultSettings.getString("color")); + assertNull(defaultSettings.getLong("someLongValue")); + + // May modify arbitrary typedef default settings after resolve + typeDef = (TypeDef) dataMgr.resolve(typeDef, null); + + defaultSettings = typeDef.getDefaultSettings(); + defaultSettings.setString("color", "red"); + defaultSettings.setLong("someLongValue", 10); assertEquals("red", defaultSettings.getString("color")); Long lv = defaultSettings.getLong("someLongValue"); assertNotNull(lv); assertEquals(10, lv.longValue()); - byte[] b = defaultSettings.getByteArray("bytes"); - assertNotNull(b); - assertEquals(3, b.length); - defaultSettings.setValue("long", new Long(10)); + defaultSettings.setValue("long", 10L); Object obj = defaultSettings.getValue("long"); assertNotNull(obj); assertEquals(10, ((Long) obj).longValue()); @@ -125,18 +142,15 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { catch (IllegalArgumentException e) { // expected } - } @Test public void testIsEmpty() throws Exception { - listing.createData(addr(10), new ByteDataType(), 1); - Data data = listing.getDataAt(addr(10)); - ByteDataType dt = (ByteDataType) data.getDataType(); + + DataType dt = dataMgr.resolve(ByteDataType.dataType, null); Settings defaultSettings = dt.getDefaultSettings(); defaultSettings.setString("color", "red"); defaultSettings.setLong("someLongValue", 10); - defaultSettings.setByteArray("bytes", new byte[] { 0, 1, 2 }); assertTrue(!defaultSettings.isEmpty()); @@ -146,28 +160,24 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testGetNames() throws Exception { - listing.createData(addr(10), new ByteDataType(), 1); - Data data = listing.getDataAt(addr(10)); - ByteDataType dt = (ByteDataType) data.getDataType(); + + DataType dt = dataMgr.resolve(ByteDataType.dataType, null); Settings defaultSettings = dt.getDefaultSettings(); defaultSettings.setString("color", "red"); defaultSettings.setLong("someLongValue", 10); - defaultSettings.setByteArray("bytes", new byte[] { 0, 1, 2 }); defaultSettings.setString("endian", "big Endian"); String[] names = defaultSettings.getNames(); - assertEquals(4, names.length); + assertEquals(3, names.length); } @Test public void testClearSetting() throws Exception { - listing.createData(addr(10), new ByteDataType(), 1); - Data data = listing.getDataAt(addr(10)); - ByteDataType dt = (ByteDataType) data.getDataType(); + + DataType dt = dataMgr.resolve(ByteDataType.dataType, null); Settings defaultSettings = dt.getDefaultSettings(); defaultSettings.setString("color", "red"); defaultSettings.setLong("someLongValue", 10); - defaultSettings.setByteArray("bytes", new byte[] { 0, 1, 2 }); defaultSettings.clearSetting("color"); assertNull(defaultSettings.getString("color")); @@ -211,11 +221,10 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { Data data = listing.getDataAt(addr(10)); data.setString("color", "red"); data.setLong("someLongValue", 10); - data.setByteArray("bytes", new byte[] { 0, 1, 2 }); data.setString("endian", "big Endian"); String[] names = data.getNames(); - assertEquals(4, names.length); + assertEquals(3, names.length); } @Test @@ -225,7 +234,6 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { data.setString("color", "red"); data.setLong("someLongValue", 10); - data.setByteArray("bytes", new byte[] { 0, 1, 2 }); data.clearSetting("color"); assertNull(data.getString("color")); @@ -238,13 +246,11 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { data.setString("color", "red"); data.setLong("someLongValue", 10); - data.setByteArray("bytes", new byte[] { 0, 1, 2 }); data.setString("endian", "big Endian"); data.clearAllSettings(); assertNull(data.getString("color")); assertNull(data.getLong("someLongValue")); - assertNull(data.getByteArray("bytes")); assertNull(data.getString("endian")); } @@ -255,7 +261,6 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { data.setString("color", "red"); data.setLong("someLongValue", 10); - data.setByteArray("bytes", new byte[] { 0, 1, 2 }); data.setString("endian", "big Endian"); assertTrue(!data.isEmpty()); @@ -271,9 +276,8 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { Address a = addr(i); dataMgr.setStringSettingsValue(a, "color", "red" + i); dataMgr.setLongSettingsValue(a, "someLongValue", i); - dataMgr.setByteSettingsValue(a, "bytes", new byte[] { 0, 1, 2 }); } - dataMgr.moveAddressRange(addr(0), addr(20), 10, TaskMonitorAdapter.DUMMY_MONITOR); + dataMgr.moveAddressRange(addr(0), addr(20), 10, TaskMonitor.DUMMY); int j = 0; for (int i = 20; i < 30; i++, j++) { Address a = addr(i); @@ -283,9 +287,6 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { Long lvalue = dataMgr.getLongSettingsValue(a, "someLongValue"); assertEquals(j, lvalue.longValue()); - - assertNotNull(dataMgr.getByteSettingsValue(a, "bytes")); - } } @@ -296,10 +297,9 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { Address a = addr(i); dataMgr.setStringSettingsValue(a, "color", "red" + i); dataMgr.setLongSettingsValue(a, "someLongValue", i); - dataMgr.setByteSettingsValue(a, "bytes", new byte[] { 0, 1, 2 }); } try { - dataMgr.moveAddressRange(addr(0), addr(5), 10, TaskMonitorAdapter.DUMMY_MONITOR); + dataMgr.moveAddressRange(addr(0), addr(5), 10, TaskMonitor.DUMMY); } catch (CancelledException e) { Assert.fail("Unexpected cancelled exception"); @@ -314,8 +314,6 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { Long lvalue = dataMgr.getLongSettingsValue(a, "someLongValue"); assertEquals(j, lvalue.longValue()); - - assertNotNull(dataMgr.getByteSettingsValue(a, "bytes")); } } @@ -327,11 +325,10 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { Address a = addr(i); dataMgr.setStringSettingsValue(a, "color", "red" + i); dataMgr.setLongSettingsValue(a, "someLongValue", i); - dataMgr.setByteSettingsValue(a, "bytes", new byte[] { 0, 1, 2 }); } j = 20; try { - dataMgr.moveAddressRange(addr(20), addr(5), 10, TaskMonitorAdapter.DUMMY_MONITOR); + dataMgr.moveAddressRange(addr(20), addr(5), 10, TaskMonitor.DUMMY); } catch (CancelledException e) { Assert.fail("Unexpected cancelled exception"); @@ -344,8 +341,6 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { Long lvalue = dataMgr.getLongSettingsValue(a, "someLongValue"); assertEquals(j, lvalue.longValue()); - - assertNotNull(dataMgr.getByteSettingsValue(a, "bytes")); } } @@ -381,89 +376,78 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testDefaultSettingsOnTypedef() throws Exception { DataType byteDT = dataMgr.resolve(new ByteDataType(), null); - SettingsDefinition[] bdefs = byteDT.getSettingsDefinitions(); + SettingsDefinition[] settingsDefinitions = byteDT.getSettingsDefinitions(); Settings settings = byteDT.getDefaultSettings(); settings.setLong("format", FormatSettingsDefinition.OCTAL); - settings.setString("color", "red"); - settings.setLong("someLongValue", 10); TypedefDataType tdt = new TypedefDataType("ByteTypedef", byteDT); TypeDef td = (TypeDef) dataMgr.addDataType(tdt, null); SettingsDefinition[] sdefs = td.getSettingsDefinitions(); - assertNotNull(sdefs); - - assertEquals(bdefs.length, sdefs.length); + assertTrue(sdefs.length >= settingsDefinitions.length); // TypeDef may add some of its own Settings defSettings = td.getDefaultSettings(); - assertNull(defSettings.getValue("format")); - assertNull(defSettings.getValue("color")); - assertNull(defSettings.getValue("someLongValue")); + defSettings.setLong("someLongValue", 10); + + assertEquals((long) FormatSettingsDefinition.OCTAL, defSettings.getValue("format")); // inherits from byteDt + assertEquals("red", defSettings.getValue("color")); // inherits from byteDt + assertEquals(10L, defSettings.getValue("someLongValue")); } @Test public void testDefaultSettingsOnTypedef2() throws Exception { DataType byteDT = dataMgr.resolve(new ByteDataType(), null); - SettingsDefinition[] bdefs = byteDT.getSettingsDefinitions(); Settings settings = byteDT.getDefaultSettings(); - Settings defaultSettings = new SettingsImpl(byteDT.getDefaultSettings()); - settings.setLong("format", FormatSettingsDefinition.OCTAL); - bdefs[0].copySetting(settings, defaultSettings); TypedefDataType tdt = new TypedefDataType("ByteTypedef", byteDT); TypeDef td = (TypeDef) dataMgr.addDataType(tdt, null); Settings defSettings = td.getDefaultSettings(); - assertNull(defSettings.getValue("format")); + defSettings.setLong("format", FormatSettingsDefinition.OCTAL); + assertEquals((long) FormatSettingsDefinition.OCTAL, defSettings.getValue("format")); // change the default settings for Byte data type; should not // affect the typedef default settings settings.setLong("format", FormatSettingsDefinition.BINARY); - bdefs[0].copySetting(settings, defaultSettings); defSettings = td.getDefaultSettings(); - assertNull(defSettings.getValue("format")); + assertEquals((long) FormatSettingsDefinition.OCTAL, defSettings.getValue("format")); } @Test public void testDefaultSettingsOnTypedefUndoRedo() throws Exception { DataType byteDT = dataMgr.resolve(new ByteDataType(), null); - SettingsDefinition[] bdefs = byteDT.getSettingsDefinitions(); Settings settings = byteDT.getDefaultSettings(); - Settings defaultSettings = new SettingsImpl(byteDT.getDefaultSettings()); settings.setLong("format", FormatSettingsDefinition.OCTAL); - bdefs[0].copySetting(settings, defaultSettings); endTransaction(); startTransaction(); TypedefDataType tdt = new TypedefDataType("ByteTypedef", byteDT); TypeDef td = (TypeDef) dataMgr.addDataType(tdt, null); Settings defSettings = td.getDefaultSettings(); - assertNull(defSettings.getValue("format")); + assertEquals((long) FormatSettingsDefinition.OCTAL, defSettings.getValue("format")); // inherits from byteDt endTransaction(); startTransaction(); defSettings.setLong("format", FormatSettingsDefinition.BINARY); + assertEquals((long) FormatSettingsDefinition.BINARY, defSettings.getValue("format")); endTransaction(); undo(program); defSettings = td.getDefaultSettings(); - assertNull(defSettings.getValue("format")); + assertEquals((long) FormatSettingsDefinition.OCTAL, defSettings.getValue("format")); // inherits from byteDt redo(program); defSettings = td.getDefaultSettings(); - assertEquals(new Long(FormatSettingsDefinition.BINARY), defSettings.getValue("format")); + assertEquals((long) FormatSettingsDefinition.BINARY, defSettings.getValue("format")); } @Test public void testDefaultSettingsOnDeletedTypdef() throws Exception { DataType byteDT = dataMgr.resolve(new ByteDataType(), null); - SettingsDefinition[] bdefs = byteDT.getSettingsDefinitions(); Settings settings = byteDT.getDefaultSettings(); - Settings defaultSettings = new SettingsImpl(byteDT.getDefaultSettings()); settings.setLong("format", FormatSettingsDefinition.OCTAL); - bdefs[0].copySetting(settings, defaultSettings); TypedefDataType tdt = new TypedefDataType("ByteTypedef", byteDT); TypeDef td = (TypeDef) dataMgr.addDataType(tdt, null); @@ -476,7 +460,7 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { endTransaction(); startTransaction(); - dataMgr.remove(td, TaskMonitorAdapter.DUMMY_MONITOR); + dataMgr.remove(td, TaskMonitor.DUMMY); endTransaction(); // make sure accessing the settings does not blow up assertTrue(td.isDeleted()); @@ -487,7 +471,7 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(!td.isDeleted()); Settings s = td.getDefaultSettings(); - assertNull(s.getValue("format")); + assertEquals((long) FormatSettingsDefinition.OCTAL, s.getValue("format")); redo(program); assertTrue(td.isDeleted()); @@ -511,6 +495,6 @@ public class SettingsTest extends AbstractGhidraHeadedIntegrationTest { Memory memory = program.getMemory(); memory.createInitializedBlock("test", addr(0), 100, (byte) 0, - TaskMonitorAdapter.DUMMY_MONITOR, false); + TaskMonitor.DUMMY, false); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/model/data/PointerTypedefDataTypeTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/model/data/PointerTypedefDataTypeTest.java new file mode 100644 index 0000000000..31d18f0d6e --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/model/data/PointerTypedefDataTypeTest.java @@ -0,0 +1,137 @@ +/* ### + * 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.program.model.data; + +import static org.junit.Assert.*; + +import org.junit.*; + +import ghidra.program.database.ProgramBuilder; +import ghidra.program.database.data.PointerTypedefInspector; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.listing.Program; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.test.TestEnv; + +/** + * Tests for manipulating data types in the category/data type tree. + */ +public class PointerTypedefDataTypeTest extends AbstractGhidraHeadedIntegrationTest { + + private TestEnv env; + private Program program; + private DataTypeManager dtm; + private BuiltInDataTypeManager builtInDtm; + + public PointerTypedefDataTypeTest() { + super(); + } + + private Program buildProgram(String programName) throws Exception { + ProgramBuilder builder = new ProgramBuilder(programName, ProgramBuilder._TOY); + builder.createMemory("test1", Long.toHexString(0x1001000), 0x2000); + return builder.getProgram(); + } + + @Before + public void setUp() throws Exception { + env = new TestEnv(); + program = buildProgram("notepad"); + dtm = program.getDataTypeManager(); + builtInDtm = BuiltInDataTypeManager.getDataTypeManager(); + + program.startTransaction("TEST"); + } + + @After + public void tearDown() throws Exception { + env.release(program); + env.dispose(); + } + + @Test + public void testIBOBuiltIn() throws Exception { + + DataType dt = builtInDtm.getDataType(CategoryPath.ROOT, IBO32DataType.NAME); + assertTrue(dt instanceof TypeDef); + assertFalse(dt instanceof BuiltIn); // transforms from BuiltIn to DataTypeDB + assertEquals(IBO32DataType.NAME, dt.getName()); + assertFalse(dt.hasLanguageDependantLength()); + assertTrue(dt.isEquivalent(dtm.resolve(dt, null))); + + dt = new IBO32DataType(CharDataType.dataType, dtm); + assertTrue(dt instanceof TypeDef); + assertTrue(dt instanceof BuiltIn); + assertEquals("char *32 __attribute__((image-base-relative))", dt.getName()); + DataType dbDt = dtm.resolve(dt, null); + assertTrue(dbDt instanceof TypeDef); + assertFalse(dbDt instanceof BuiltIn); // transforms from BuiltIn to DataTypeDB + assertTrue(dt.isEquivalent(dbDt)); + + dt = builtInDtm.getDataType(CategoryPath.ROOT, IBO64DataType.NAME); + assertTrue(dt instanceof TypeDef); + assertFalse(dt instanceof BuiltIn); // transforms from BuiltIn to DataTypeDB + assertEquals(IBO64DataType.NAME, dt.getName()); + assertFalse(dt.hasLanguageDependantLength()); + assertTrue(dt.isEquivalent(dtm.resolve(dt, null))); + + dt = new IBO64DataType(CharDataType.dataType, dtm); + assertTrue(dt instanceof TypeDef); + assertTrue(dt instanceof BuiltIn); + assertEquals("char *64 __attribute__((image-base-relative))", dt.getName()); + dbDt = dtm.resolve(dt, null); + assertTrue(dbDt instanceof TypeDef); + assertFalse(dbDt instanceof BuiltIn); // transforms from BuiltIn to DataTypeDB + assertTrue(dt.isEquivalent(dbDt)); + } + + @Test + public void testPointerTypedef() throws Exception { + + DataType dt = new PointerTypedef(null, CharDataType.dataType, -1, dtm, + program.getAddressFactory().getRegisterSpace()); + assertTrue(dt.hasLanguageDependantLength()); + assertEquals(4, dt.getLength()); + assertTrue(dt instanceof TypeDef); + assertTrue(dt instanceof BuiltIn); + assertEquals("char * __attribute__((space(register)))", dt.getName()); + DataType dbDt = dtm.resolve(dt, null); + assertTrue(dbDt instanceof TypeDef); + assertFalse(dbDt instanceof BuiltIn); // transforms from BuiltIn to DataTypeDB + assertTrue(dt.isEquivalent(dbDt)); + + AddressSpace space = PointerTypedefInspector.getPointerAddressSpace((TypeDef) dbDt, + program.getAddressFactory()); + assertTrue(program.getAddressFactory().getRegisterSpace().equals(space)); + + dt = new PointerTypedef(null, CharDataType.dataType, -1, dtm, + PointerType.RELATIVE); + assertTrue(dt.hasLanguageDependantLength()); + assertEquals(4, dt.getLength()); + assertTrue(dt instanceof TypeDef); + assertTrue(dt instanceof BuiltIn); + assertEquals("char * __attribute__((relative))", dt.getName()); + dbDt = dtm.resolve(dt, null); + assertTrue(dbDt instanceof TypeDef); + assertFalse(dbDt instanceof BuiltIn); // transforms from BuiltIn to DataTypeDB + assertTrue(dt.isEquivalent(dbDt)); + + assertEquals(PointerType.RELATIVE, PointerTypedefInspector.getPointerType((TypeDef) dbDt)); + + } + + +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/PcodeFunctionParser.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/PcodeFunctionParser.java index 1eaaab6080..573f35a6be 100644 --- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/PcodeFunctionParser.java +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/PcodeFunctionParser.java @@ -158,10 +158,10 @@ public class PcodeFunctionParser { MemoryBufferImpl memoryBuffer = new MemoryBufferImpl(this.program.getMemory(), ramSpaceAddress); - SettingsImpl settings = new SettingsImpl(); StringDataInstance stringDataInstance = StringDataInstance - .getStringDataInstance(new StringDataType(), memoryBuffer, settings, BUFFER_LENGTH); + .getStringDataInstance(new StringDataType(), memoryBuffer, SettingsImpl.NO_SETTINGS, + BUFFER_LENGTH); String stringValue = stringDataInstance.getStringValue(); if (stringValue == null) { return null; diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/analyzers/FileFormatAnalyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/analyzers/FileFormatAnalyzer.java index ca96b658f2..e42480c409 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/analyzers/FileFormatAnalyzer.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/analyzers/FileFormatAnalyzer.java @@ -21,7 +21,8 @@ import ghidra.app.cmd.data.CreateStringCmd; import ghidra.app.cmd.function.CreateFunctionCmd; import ghidra.app.services.*; import ghidra.app.util.importer.MessageLog; -import ghidra.docking.settings.*; +import ghidra.docking.settings.FormatSettingsDefinition; +import ghidra.docking.settings.SettingsDefinition; import ghidra.framework.options.Options; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; @@ -140,8 +141,6 @@ public abstract class FileFormatAnalyzer implements Analyzer { } protected void changeFormatToString(Data data) { - SettingsImpl settings = new SettingsImpl(data); - settings.setDefaultSettings(settings); SettingsDefinition[] settingsDefinitions = data.getDataType().getSettingsDefinitions(); for (SettingsDefinition settingsDefinition : settingsDefinitions) { if (settingsDefinition instanceof FormatSettingsDefinition) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/builder/AbstractActionBuilder.java b/Ghidra/Framework/Docking/src/main/java/docking/action/builder/AbstractActionBuilder.java index eee4b56163..6da9984994 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/builder/AbstractActionBuilder.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/builder/AbstractActionBuilder.java @@ -52,7 +52,9 @@ import resources.ResourceManager; * the {@link #withContext(Class)} call. */ public abstract class AbstractActionBuilder> { + private final Predicate ALWAYS_TRUE = e -> true; + /** * Name for the {@code DockingAction} */ @@ -163,9 +165,10 @@ public abstract class AbstractActionBuilder enabledPredicate = null; /** - * Predicate for determining if an action should be included on the pop-up menu + * Predicate for determining if an action should be included on the pop-up menu. + * A null popupPredicate will cause default behavior which defers to the enabledPredicate. */ - private Predicate popupPredicate = ALWAYS_TRUE; + private Predicate popupPredicate = null; /** * Predicate for determining if an action is applicable for a given context @@ -547,8 +550,7 @@ public abstract class AbstractActionBuilderNote: use this method when you wish for an action to be added to a popup menu regardless * of whether it is enabled. As mentioned above, standard popup actions will only be added @@ -734,7 +736,9 @@ public abstract class AbstractActionBuilder *
  • Have a max value - a max value can be set (must be positive) such that the user can not type a - * number greater than the max. Otherwise, the number is unlimited. See {@link #setMaxValue(BigInteger)}
  • + * number whose absolute value is greater than the max. Otherwise, the value is unlimited if max is + * null/unspecified. See {@link #setMaxValue(BigInteger)} *
  • Show the number mode as hint text - If on either "Hex" or "Dec" is displayed lightly in the * bottom right portion of the text field. See {@link #setShowNumberMode(boolean)}
  • * @@ -218,7 +218,7 @@ public class IntegerTextField { */ public void setValue(BigInteger newValue) { - if (!allowsNegative && newValue != null && newValue.compareTo(BigInteger.ZERO) < 0) { + if (!allowsNegative && newValue != null && newValue.signum() < 0) { newValue = null; } @@ -304,7 +304,7 @@ public class IntegerTextField { BigInteger currentValue = getValue(); allowsNegative = b; if (!allowsNegative) { - if (currentValue != null && currentValue.compareTo(BigInteger.ZERO) < 0) { + if (currentValue != null && currentValue.signum() < 0) { currentValue = null; } } @@ -313,7 +313,8 @@ public class IntegerTextField { /** * Returns the current maximum allowed value. Null indicates that there is no maximum value. - * + * If negative values are permitted (see {@link #setAllowNegativeValues(boolean)}) this value + * will establish the upper and lower limit of the absolute value. * @return the current maximum value allowed. */ public BigInteger getMaxValue() { @@ -323,17 +324,25 @@ public class IntegerTextField { /** * Sets the maximum allowed value. The maximum must be a positive number. Null indicates that * there is no maximum value. + *

    + * If negative values are permitted (see {@link #setAllowNegativeValues(boolean)}) this value + * will establish the upper and lower limit of the absolute value. * * @param maxValue the maximum value to allow. */ public void setMaxValue(BigInteger maxValue) { - if (maxValue != null && maxValue.compareTo(BigInteger.ZERO) < 0) { + if (maxValue != null && maxValue.signum() < 0) { throw new IllegalArgumentException("Max value must be positive"); } BigInteger currentValue = getValue(); this.maxValue = maxValue; - if (!passesMaxCheck(currentValue)) { - setValue(maxValue); + if (maxValue != null && !passesMaxCheck(currentValue)) { + if (currentValue.signum() < 0) { + setValue(maxValue.negate()); + } + else { + setValue(maxValue); + } } } @@ -460,8 +469,7 @@ public class IntegerTextField { if (maxValue == null) { return true; } - - return value.compareTo(maxValue) <= 0; + return value.abs().compareTo(maxValue) <= 0; } private void updateNumberMode(String text) { diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/BooleanSettingsDefinition.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/BooleanSettingsDefinition.java index 87d65f6d23..ddf4fa8353 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/BooleanSettingsDefinition.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/BooleanSettingsDefinition.java @@ -36,4 +36,9 @@ public interface BooleanSettingsDefinition extends SettingsDefinition { */ public abstract void setValue(Settings settings, boolean value); + @Override + public default boolean hasSameValue(Settings settings1, Settings settings2) { + return getValue(settings1) == getValue(settings2); + } + } diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/EnumSettingsDefinition.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/EnumSettingsDefinition.java index 84d76f4d7e..ea14d51170 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/EnumSettingsDefinition.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/EnumSettingsDefinition.java @@ -50,4 +50,9 @@ public interface EnumSettingsDefinition extends SettingsDefinition { */ public String[] getDisplayChoices(Settings settings); + @Override + public default boolean hasSameValue(Settings settings1, Settings settings2) { + return getChoice(settings1) == getChoice(settings2); + } + } diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/FloatingPointPrecisionSettingsDefinition.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/FloatingPointPrecisionSettingsDefinition.java index b39e426468..00bd9597e9 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/FloatingPointPrecisionSettingsDefinition.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/FloatingPointPrecisionSettingsDefinition.java @@ -58,6 +58,11 @@ public class FloatingPointPrecisionSettingsDefinition implements EnumSettingsDef return value.intValue(); } + @Override + public String getValueString(Settings settings) { + return Integer.toString(getPrecision(settings)); + } + @Override public void setChoice(Settings settings, int valueIndex) { diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/FormatSettingsDefinition.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/FormatSettingsDefinition.java index 073a8fd20e..c1b934d1e3 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/FormatSettingsDefinition.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/FormatSettingsDefinition.java @@ -89,6 +89,11 @@ public class FormatSettingsDefinition implements EnumSettingsDefinition { return getFormat(settings); } + @Override + public String getValueString(Settings settings) { + return choices[getChoice(settings)]; + } + @Override public void setChoice(Settings settings, int value) { if (value < 0 || value > CHAR) { diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/IntegerSignednessFormattingModeSettingsDefinition.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/IntegerSignednessFormattingModeSettingsDefinition.java index 090e1ba6e4..ace9083fdb 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/IntegerSignednessFormattingModeSettingsDefinition.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/IntegerSignednessFormattingModeSettingsDefinition.java @@ -51,7 +51,7 @@ public class IntegerSignednessFormattingModeSettingsDefinition implements EnumSe return defaultFormat; } Long value = settings.getLong(SIGN_FORMAT); - if (value == null) { + if (value == null || value < 0 || value >= choices.length) { return defaultFormat; } int format = (int) value.longValue(); @@ -72,6 +72,11 @@ public class IntegerSignednessFormattingModeSettingsDefinition implements EnumSe return getFormatMode(settings).ordinal(); } + @Override + public String getValueString(Settings settings) { + return choices[getChoice(settings)]; + } + /** * Set, or clear if mode is null, the new mode in the provided settings * @param settings settings object diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/JavaEnumSettingsDefinition.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/JavaEnumSettingsDefinition.java index aab456b454..d4347b0124 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/JavaEnumSettingsDefinition.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/JavaEnumSettingsDefinition.java @@ -174,6 +174,11 @@ public class JavaEnumSettingsDefinition> implements EnumSettin return Math.min(Math.max(value, 0), values.length - 1); } + @Override + public String getValueString(Settings settings) { + return values[getChoice(settings)].toString(); + } + @Override public void setChoice(Settings settings, int value) { settings.setLong(getSettingName(), value); diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/NumberSettingsDefinition.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/NumberSettingsDefinition.java new file mode 100644 index 0000000000..fb189c537f --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/NumberSettingsDefinition.java @@ -0,0 +1,78 @@ +/* ### + * 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.docking.settings; + +import java.math.BigInteger; + +import ghidra.util.BigEndianDataConverter; + +public interface NumberSettingsDefinition extends SettingsDefinition { + + /** + * Gets the value for this SettingsDefinition given a Settings object. + * @param settings the set of Settings values for a particular location or null for default value. + * @return the value for this settings object given the context. + */ + public abstract long getValue(Settings settings); + + /** + * Sets the given value into the given settings object using this settingsDefinition as the key. + * @param settings the settings object to store the value in. + * @param value the value to store in the settings object using this settingsDefinition as the key. + */ + public abstract void setValue(Settings settings, long value); + + /** + * Get the maximum value permitted. The absolute value of the setting may not exceed this value. + * @return maximum value permitted + */ + public abstract BigInteger getMaxValue(); + + /** + * Determine if negative values are permitted. + * @return true if negative values are permitted, else false. + */ + public abstract boolean allowNegativeValue(); + + /** + * Determine if hexidecimal entry/display is preferred due to the + * nature of the setting (e.g., mask) + * @return true if hex preferred over decimal, else false + */ + public boolean isHexModePreferred(); + + @Override + public default String getValueString(Settings settings) { + long value = getValue(settings); + if (!allowNegativeValue()) { + byte[] bytes = BigEndianDataConverter.INSTANCE.getBytes(value); + BigInteger unsignedValue = new BigInteger(1, bytes); + return "0x" + unsignedValue.toString(16); + } + BigInteger signedValue = BigInteger.valueOf(value); + String sign = ""; + if (signedValue.signum() < 0) { + sign = "-"; + signedValue = signedValue.negate(); + } + return sign + "0x" + signedValue.toString(16); + } + + @Override + public default boolean hasSameValue(Settings settings1, Settings settings2) { + return getValue(settings1) == getValue(settings2); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/Settings.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/Settings.java index 689c55c16d..03740c3783 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/Settings.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/Settings.java @@ -35,13 +35,6 @@ public interface Settings { * @return the String value for a key */ String getString(String name); - - /** - * Gets the byte[] value associated with the given name - * @param name the key used to retrieve a value - * @return the byte[] value for a key - */ - byte[] getByteArray(String name); /** * Gets the object associated with the given name @@ -56,18 +49,13 @@ public interface Settings { * @param value the value associated with the key */ void setLong(String name, long value); + /** * Associates the given String value with the name * @param name the key * @param value the value associated with the key */ void setString(String name, String value); - /** - * Associates the given byte[] with the name - * @param name the key - * @param value the value associated with the key - */ - void setByteArray(String name, byte[] value); /** * Associates the given object with the name @@ -94,18 +82,16 @@ public interface Settings { String[] getNames(); /** - * Returns true if there are no key-value pairs stored in this settings object + * Returns true if there are no key-value pairs stored in this settings object. + * This is not a reflection of the underlying default settings which may still + * contain a key-value pair when this settings object is empty. + * @return true if there are no key-value pairs stored in this settings object */ boolean isEmpty(); -// /** -// * Sets the settings object to use if this settings object does not have the requested settings name. -// * @param settings the settings object to use if this settings object does not have the requested settings name. -// */ -// void setDefaultSettings(Settings settings); - /** * Returns the underlying default settings for these settings or null if there are none + * @return underlying default settings or null */ Settings getDefaultSettings(); diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/SettingsDefinition.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/SettingsDefinition.java index db978a2b36..01c78d0723 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/SettingsDefinition.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/SettingsDefinition.java @@ -15,6 +15,9 @@ */ package ghidra.docking.settings; +import java.util.ArrayList; +import java.util.function.Predicate; + /** * Generic interface for defining display options on data and dataTypes. Uses * Settings objects to store values which are interpreted by SettingsDefinition objects. @@ -44,15 +47,48 @@ public interface SettingsDefinition { return result; } + /** + * Get datatype settings definitions for the specified datatype exclusive of any default-use-only definitions. + * @param definitions settings definitions to be filtered + * @param filter callback which determines if definition should be included in returned array + * @return filtered settings definitions + */ + public static SettingsDefinition[] filterSettingsDefinitions(SettingsDefinition[] definitions, + Predicate filter) { + ArrayList list = new ArrayList<>(); + for (SettingsDefinition def : definitions) { + if (filter.test(def)) { + list.add(def); + } + } + SettingsDefinition[] defs = new SettingsDefinition[list.size()]; + return list.toArray(defs); + } + + /** + * Determine if a setting value has been stored + * @param setting stored settings + * @return true if a value has been stored, else false + */ public boolean hasValue(Settings setting); + /** + * Get the setting value as a string which corresponds to this definition. + * A default value string will be returned if a setting has not been stored. + * @param settings settings + * @return value string or null if not set and default has not specified by this definition + */ + public String getValueString(Settings settings); + /** * Returns the name of this SettingsDefinition + * @return display name for setting */ public String getName(); /** * Returns a description of this settings definition + * @return setting description */ public String getDescription(); @@ -70,4 +106,13 @@ public interface SettingsDefinition { */ public void copySetting(Settings srcSettings, Settings destSettings); + /** + * Check two settings for equality which correspond to this + * settings definition. + * @param settings1 first settings + * @param settings2 second settings + * @return true if the same else false + */ + public boolean hasSameValue(Settings settings1, Settings settings2); + } diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/SettingsImpl.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/SettingsImpl.java index 93a205db00..228c26418a 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/SettingsImpl.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/SettingsImpl.java @@ -18,9 +18,14 @@ package ghidra.docking.settings; import java.io.Serializable; import java.util.*; +import javax.help.UnsupportedOperationException; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import com.google.common.base.Predicate; + +import ghidra.util.Msg; + /** * Basic implementation of the Settings object */ @@ -31,24 +36,72 @@ public class SettingsImpl implements Settings, Serializable { private Settings defaultSettings; private ChangeListener listener; private Object changeSourceObj; + private boolean immutable; + private Predicate allowedSettingPredicate; //@formatter:off - public static final Settings NO_SETTINGS = new SettingsImpl() { - @Override public void setByteArray(String name, byte[] value) { /* nada */ } - @Override public void setDefaultSettings(Settings settings) { /* nada*/ } - @Override public void setLong(String name, long value) { /* nada */ } - @Override public void setString(String name, String value) { /* nada */ } - @Override public void setValue(String name, Object value) { /* nada */ } + public static final Settings NO_SETTINGS = new SettingsImpl(true) { + @Override + public void setDefaultSettings(Settings settings) { + throw new UnsupportedOperationException(); + } }; //@formatter:on /** - * Construct a new SettingsImpl + * Construct a new SettingsImpl. */ public SettingsImpl() { map = new HashMap<>(); } + /** + * Construct a new SettingsImpl with a modification predicate. + * @param allowedSettingPredicate callback for checking an allowed setting modification + */ + public SettingsImpl(Predicate allowedSettingPredicate) { + map = new HashMap<>(); + this.allowedSettingPredicate = allowedSettingPredicate; + } + + /** + * Check for immutable settings and log error of modification not permitted + * @param type setting type or null + * @param name setting name or null + * @return true if change permitted + */ + private boolean checkSetting(String type, String name) { + if (immutable) { + String typeStr = ""; + if (type != null) { + typeStr = type + " "; + } + String nameStr = ": " + name; + if (name == null) { + nameStr = "s"; + } + Msg.warn(SettingsImpl.class, + "Ignored invalid attempt to modify immutable " + typeStr + "component setting" + + nameStr); + return false; + } + if (allowedSettingPredicate != null && !allowedSettingPredicate.apply(name)) { + Msg.warn(this, "Ignored disallowed setting '" + name + "'"); + return false; + } + return true; + } + + /** + * Construct a new SettingsImpl. + * @param immutable if true settings are immutable with the exception of + * setting its default settings. If false settings may be modified. + */ + public SettingsImpl(boolean immutable) { + this.immutable = immutable; + map = new HashMap<>(); + } + /** * Construct a new SettingsImpl with the given listener * @param listener object to be notified as settings values change @@ -61,15 +114,18 @@ public class SettingsImpl implements Settings, Serializable { } /** - * Construct a new SettingsImpl object with the same set of name-value pairs - * as the given settings object + * Construct a new SettingsImpl object. If settings object is specified this + * settings will copy all name/value pairs and underlying defaults. * @param settings the settings object to copy */ public SettingsImpl(Settings settings) { this(); - String[] names = settings.getNames(); - for (int i = 0; i < names.length; i++) { - map.put(names[i], settings.getValue(names[i])); + if (settings != null) { + String[] names = settings.getNames(); + for (int i = 0; i < names.length; i++) { + map.put(names[i], settings.getValue(names[i])); + } + defaultSettings = settings.getDefaultSettings(); } } @@ -78,18 +134,11 @@ public class SettingsImpl implements Settings, Serializable { return map.toString(); } - /** - * - * @see ghidra.docking.settings.Settings#isEmpty() - */ @Override public boolean isEmpty() { return map.isEmpty(); } - /** - * @see ghidra.docking.settings.Settings#getLong(java.lang.String) - */ @Override public Long getLong(String name) { Long value = (Long) map.get(name); @@ -99,70 +148,41 @@ public class SettingsImpl implements Settings, Serializable { return value; } - /** - * @see ghidra.docking.settings.Settings#getString(java.lang.String) - */ @Override public String getString(String name) { - String value = (String) map.get(name); - if (value == null && defaultSettings != null) { - value = defaultSettings.getString(name); + if (map.containsKey(name)) { + return (String) map.get(name); // null may be allowed/set } - return value; + if (defaultSettings != null) { + return defaultSettings.getString(name); + } + return null; } - /** - * @see ghidra.docking.settings.Settings#getByteArray(java.lang.String) - */ - @Override - public byte[] getByteArray(String name) { - byte[] bytes = (byte[]) map.get(name); - if (bytes == null && defaultSettings != null) { - bytes = defaultSettings.getByteArray(name); - } - return bytes; - } - - /** - * @see ghidra.docking.settings.Settings#setLong(java.lang.String, long) - */ @Override public void setLong(String name, long value) { - map.put(name, new Long(value)); - changed(); + if (checkSetting("long", name)) { + map.put(name, Long.valueOf(value)); + changed(); + } } - /** - * @see ghidra.docking.settings.Settings#setString(java.lang.String, java.lang.String) - */ @Override public void setString(String name, String value) { - map.put(name, value); - changed(); + if (checkSetting("string", name)) { + map.put(name, value); + changed(); + } } - /** - * @see ghidra.docking.settings.Settings#setByteArray(java.lang.String, byte[]) - */ - @Override - public void setByteArray(String name, byte[] value) { - map.put(name, value); - changed(); - } - - /** - * - * @see ghidra.docking.settings.Settings#clearSetting(java.lang.String) - */ @Override public void clearSetting(String name) { - map.remove(name); - changed(); + if (checkSetting(null, name)) { + map.remove(name); + changed(); + } } - /** - * @see ghidra.docking.settings.Settings#getNames() - */ @Override public String[] getNames() { String[] names = new String[map.size()]; @@ -174,9 +194,6 @@ public class SettingsImpl implements Settings, Serializable { return names; } - /** - * @see ghidra.docking.settings.Settings#getValue(java.lang.String) - */ @Override public Object getValue(String name) { Object value = map.get(name); @@ -186,11 +203,11 @@ public class SettingsImpl implements Settings, Serializable { return value; } - /** - * @see ghidra.docking.settings.Settings#setValue(java.lang.String, java.lang.Object) - */ @Override public void setValue(String name, Object value) { + if (!checkSetting(null, name)) { + return; + } if (value instanceof Long || value instanceof String || value instanceof byte[]) { map.put(name, value); changed(); @@ -209,22 +226,18 @@ public class SettingsImpl implements Settings, Serializable { } } - /** - * @see ghidra.docking.settings.Settings#clearAllSettings() - */ @Override public void clearAllSettings() { - map.clear(); - changed(); + if (checkSetting(null, null)) { + map.clear(); + changed(); + } } public void setDefaultSettings(Settings settings) { defaultSettings = settings; } - /** - * @see ghidra.docking.settings.Settings#getDefaultSettings() - */ @Override public Settings getDefaultSettings() { return defaultSettings; diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/StringSettingsDefinition.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/StringSettingsDefinition.java new file mode 100644 index 0000000000..0ba49b226a --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/settings/StringSettingsDefinition.java @@ -0,0 +1,46 @@ +/* ### + * 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.docking.settings; + +import java.util.Objects; + +public interface StringSettingsDefinition extends SettingsDefinition { + + /** + * Gets the value for this SettingsDefinition given a Settings object. + * @param settings the set of Settings values for a particular location or null for default value. + * @return the value for this settings object given the context. + */ + public abstract String getValue(Settings settings); + + /** + * Sets the given value into the given settings object using this settingsDefinition as the key. + * @param settings the settings object to store the value in. + * @param value the value to store in the settings object using this settingsDefinition as the key. + */ + public abstract void setValue(Settings settings, String value); + + @Override + public default String getValueString(Settings settings) { + String str = getValue(settings); + return str != null ? str : ""; + } + + @Override + public default boolean hasSameValue(Settings settings1, Settings settings2) { + return Objects.equals(getValue(settings1), getValue(settings2)); + } +} diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/util/VersionExceptionHandler.java b/Ghidra/Framework/Project/src/main/java/ghidra/util/VersionExceptionHandler.java index b4c899d8c5..0e58cf9abc 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/util/VersionExceptionHandler.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/util/VersionExceptionHandler.java @@ -85,7 +85,7 @@ public class VersionExceptionHandler { String message = "This " + contentType + " file is shared with other users. If you upgrade this file,\n" + "other users will not be able to read the new version until they upgrade to \n" + - "the same version of Ghidra. Do you want to continue?"; + "a compatible version of Ghidra. Do you want to continue?"; return OptionDialog.showOptionDialog(parent, title, message, "Upgrade", OptionDialog.WARNING_MESSAGE); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/PseudoData.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/PseudoData.java index e53b99a623..bd837d1457 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/PseudoData.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/PseudoData.java @@ -19,7 +19,7 @@ import java.util.*; import ghidra.docking.settings.Settings; import ghidra.program.database.ProgramDB; -import ghidra.program.database.data.DataTypeManagerDB; +import ghidra.program.database.data.ProgramDataTypeManager; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressOverflowException; import ghidra.program.model.data.*; @@ -40,7 +40,7 @@ public class PseudoData extends PseudoCodeUnit implements Data { protected static final int OP_INDEX = 0; protected int level = 0; - protected DataTypeManagerDB dataMgr; + protected ProgramDataTypeManager dataMgr; private static final int[] EMPTY_PATH = new int[0]; @@ -127,7 +127,7 @@ public class PseudoData extends PseudoCodeUnit implements Data { new PseudoDataComponent(program, address.add(dtc.getOffset()), this, dtc, this); } } - catch (MemoryAccessException | AddressOverflowException e) { + catch (AddressOverflowException e) { throw new ConcurrentModificationException("Data type length changed"); } return data; @@ -226,11 +226,6 @@ public class PseudoData extends PseudoCodeUnit implements Data { throw new UnsupportedOperationException(); } - @Override - public byte[] getByteArray(String name) { - return null; - } - @Override public Long getLong(String name) { return null; @@ -254,11 +249,6 @@ public class PseudoData extends PseudoCodeUnit implements Data { return null; } - @Override - public void setByteArray(String name, byte[] value) { - throw new UnsupportedOperationException(); - } - @Override public void setLong(String name, long value) { throw new UnsupportedOperationException(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/PseudoDataComponent.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/PseudoDataComponent.java index fca1c1b021..cbd8432f00 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/PseudoDataComponent.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/PseudoDataComponent.java @@ -21,7 +21,8 @@ import ghidra.program.model.address.AddressOverflowException; import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataTypeComponent; import ghidra.program.model.listing.*; -import ghidra.program.model.mem.*; +import ghidra.program.model.mem.MemBuffer; +import ghidra.program.model.mem.WrappedMemBuffer; /** * DataComponent provides Data and CodeUnit access to Struct and Array components. @@ -39,14 +40,9 @@ class PseudoDataComponent extends PseudoData { private int[] path; private Settings defaultSettings; - /** - * @throws AddressOverflowException - * @throws MemoryAccessException - * - */ PseudoDataComponent(Program program, Address address, PseudoData parent, DataTypeComponent component, MemBuffer memBuffer) - throws AddressOverflowException, MemoryAccessException { + throws AddressOverflowException { super(program, address, component.getDataType(), new WrappedMemBuffer(memBuffer, component.getOffset())); this.indexInParent = component.getOrdinal(); @@ -68,10 +64,6 @@ class PseudoDataComponent extends PseudoData { this.length = length; } - /** - * - * @see ghidra.program.model.listing.Data#getComponentPath() - */ @Override public int[] getComponentPath() { if (path == null) { @@ -89,12 +81,6 @@ class PseudoDataComponent extends PseudoData { return path; } - /** - * Get the name of this Data that is a component of another - * Data Item. - * @return the name as a component of another prototype, - * and null if this is not a component of another prototype. - */ @Override public String getFieldName() { if (component == null) { // is array? @@ -109,18 +95,12 @@ class PseudoDataComponent extends PseudoData { return myName; } - /** - * Returns the path name (dot notation) for this field - */ @Override public String getPathName() { String parentPath = parent.getPathName(); return getComponentName(parentPath); } - /** - * @return the relative path name (dot notation) for this field - */ @Override public String getComponentPathName() { String parentPath = parent.getComponentPathName(); @@ -146,55 +126,31 @@ class PseudoDataComponent extends PseudoData { return nameBuffer.toString(); } - /** - * Get the immediate parent Data Prototype of this component - */ @Override public Data getParent() { return parent; } - /** - * Get the highest level Data Prototype in a hierarchy of structures - * containing this component. - */ @Override public Data getRoot() { return parent.getRoot(); } - /** - * Get the offset of this Data item from the start of - * some hierarchy of structures. - */ @Override public int getRootOffset() { return parent.getRootOffset() + getParentOffset(); } - /** - * Get the offset of this Data item from the start of its immediate - * parent. - */ @Override public int getParentOffset() { return offset; } - /** - * Get the index of this Data item within its parent - * - * @return the index of this component in its parent - * returns -1 if this is not a component - */ @Override public int getComponentIndex() { return indexInParent; } - /** - * Returns whether some other object is "equal to" this one. - */ @Override public boolean equals(Object obj) { @@ -214,30 +170,6 @@ class PseudoDataComponent extends PseudoData { return super.equals(obj); } - /** - * @see ghidra.docking.settings.Settings#getByteArray(java.lang.String) - */ - @Override - public byte[] getByteArray(String name) { - if (dataMgr == null) { - return null; - } - byte[] settingBytes = dataMgr.getByteSettingsValue(address, name); - if (settingBytes != null) { - return settingBytes; - } - if (component == null) { - return null; - } - if (defaultSettings == null) { - defaultSettings = component.getDefaultSettings(); - } - return defaultSettings.getByteArray(name); - } - - /** - * @see ghidra.docking.settings.Settings#getLong(java.lang.String) - */ @Override public Long getLong(String name) { if (dataMgr == null) { @@ -256,9 +188,6 @@ class PseudoDataComponent extends PseudoData { return defaultSettings.getLong(name); } - /** - * @see ghidra.docking.settings.Settings#getString(java.lang.String) - */ @Override public String getString(String name) { if (dataMgr == null) { @@ -277,9 +206,6 @@ class PseudoDataComponent extends PseudoData { return defaultSettings.getString(name); } - /** - * @see ghidra.docking.settings.Settings#getValue(java.lang.String) - */ @Override public Object getValue(String name) { if (dataMgr == null) { @@ -298,9 +224,6 @@ class PseudoDataComponent extends PseudoData { return defaultSettings.getValue(name); } - /** - * @see ghidra.program.model.listing.CodeUnit#getComment(int) - */ @Override public synchronized String getComment(int commentType) { String cmt = super.getComment(commentType); @@ -310,9 +233,6 @@ class PseudoDataComponent extends PseudoData { return cmt; } - /** - * @see ghidra.docking.settings.Settings#getDefaultSettings() - */ @Override public Settings getDefaultSettings() { if (component != null) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DBStringMapAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DBStringMapAdapter.java new file mode 100644 index 0000000000..571bdbc189 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DBStringMapAdapter.java @@ -0,0 +1,95 @@ +/* ### + * 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.program.database; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import db.*; + +/** + * DBStringMapAdapter provides a simple string-to-string map backed by a named database table. + * This adapter's schema must never change. + */ +public class DBStringMapAdapter { + + private final static Field[] COL_FIELDS = new Field[] { StringField.INSTANCE }; + private final static String[] COL_TYPES = new String[] { "Value" }; + private final static Schema SCHEMA = + new Schema(0, StringField.INSTANCE, "Key", COL_FIELDS, COL_TYPES); + + private Table table; + + public DBStringMapAdapter(DBHandle dbHandle, String tableName, boolean create) + throws IOException { + if (create) { + table = dbHandle.createTable(tableName, SCHEMA); + } + else { + table = dbHandle.getTable(tableName); + if (table == null) { + throw new IOException("Table not found: " + tableName); + } + } + } + + public void put(String key, String value) throws IOException { + DBRecord record = SCHEMA.createRecord(new StringField(key)); + record.setString(0, value); + table.putRecord(record); + } + + public String get(String key) throws IOException { + DBRecord record = table.getRecord(new StringField(key)); + return record != null ? record.getString(0) : null; + } + + public int getInt(String key, int defaultValue) throws IOException { + DBRecord record = table.getRecord(new StringField(key)); + if (record != null) { + try { + return Integer.valueOf(record.getString(0)); + } + catch (NumberFormatException e) { + // ignore parse error + } + } + return defaultValue; + } + + public boolean getBoolean(String key, boolean defaultValue) throws IOException { + DBRecord record = table.getRecord(new StringField(key)); + if (record != null) { + return Boolean.valueOf(record.getString(0)); + } + return defaultValue; + } + + public Set keySet() throws IOException { + HashSet set = new HashSet<>(); + DBFieldIterator keyIter = table.fieldKeyIterator(); + while (keyIter.hasNext()) { + set.add(keyIter.next().getString()); + } + return set; + } + + public void delete(String key) throws IOException { + table.deleteRecord(new StringField(key)); + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DatabaseObject.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DatabaseObject.java index af82eeb060..dd4eeb2e34 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DatabaseObject.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/DatabaseObject.java @@ -60,7 +60,7 @@ public abstract class DatabaseObject { /** * Marks the object as deleted. */ - void setDeleted() { + protected void setDeleted() { deleted = true; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java index 5ac129ed81..961d82f4f5 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramDB.java @@ -19,7 +19,8 @@ import java.io.IOException; import java.math.BigInteger; import java.util.*; -import db.*; +import db.DBConstants; +import db.DBHandle; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.framework.Application; import ghidra.framework.data.DomainObjectAdapterDB; @@ -47,9 +48,9 @@ import ghidra.program.database.util.AddressSetPropertyMapDB; import ghidra.program.model.address.*; import ghidra.program.model.lang.*; import ghidra.program.model.listing.*; -import ghidra.program.model.mem.*; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.model.mem.MemoryConflictException; import ghidra.program.model.pcode.Varnode; -import ghidra.program.model.reloc.RelocationTable; import ghidra.program.model.symbol.*; import ghidra.program.model.util.AddressSetPropertyMap; import ghidra.program.model.util.PropertyMapManager; @@ -98,9 +99,10 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM * created tables. * 18-Feb-2021 - version 23 Added support for Big Reflist for tracking FROM references. * Primarily used for large numbers of Entry Point references. - * 31-Mar-2021 - version 24 Added support for CompilerSpec extensions + * 31-Mar-2021 - version 24 Added support for CompilerSpec extensions + * 12-Jan-2022 - version 25 Added support for resolved TypeDefSettingsDefinition */ - static final int DB_VERSION = 24; + static final int DB_VERSION = 25; /** * UPGRADE_REQUIRED_BFORE_VERSION should be changed to DB_VERSION anytime the @@ -121,8 +123,9 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM public static final int COMPOUND_VARIABLE_STORAGE_ADDED_VERSION = 18; public static final int AUTO_PARAMETERS_ADDED_VERSION = 19; - private static final String LANG_DEFAULT_VERSION = "1.0"; + private static final String DATA_MAP_TABLE_NAME = "Program"; + // Data map keys private static final String PROGRAM_NAME = "Program Name"; private static final String PROGRAM_DB_VERSION = "DB Version"; private static final String LANGUAGE_VERSION = "Language Version"; @@ -134,16 +137,10 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM private static final String EXECUTABLE_FORMAT = "Executable Format"; private static final String EXECUTABLE_MD5 = "Executable MD5"; private static final String EXECUTABLE_SHA256 = "Executable SHA256"; - private static final String TABLE_NAME = "Program"; private static final String EXECUTE_PATH = "Execute Path"; private static final String EXECUTE_FORMAT = "Execute Format"; private static final String IMAGE_OFFSET = "Image Offset"; - private final static Field[] COL_FIELDS = new Field[] { StringField.INSTANCE }; - private final static String[] COL_TYPES = new String[] { "Value" }; - private final static Schema SCHEMA = - new Schema(0, StringField.INSTANCE, "Key", COL_FIELDS, COL_TYPES); - // // The numbering of managers controls the order in which they are notified. // The following ManagerDB methods are invoked for each manager starting with index 0: @@ -188,7 +185,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM private AddressMapDB addrMap; private ListingDB listing; private ProgramUserDataDB programUserData; - private Table table; + private DBStringMapAdapter dataMap; private Language language; private CompilerSpec compilerSpec; @@ -240,7 +237,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM try { int id = startTransaction("create program"); - createDatabase(); + createProgramInfo(); if (createManagers(CREATE, TaskMonitor.DUMMY) != null) { throw new AssertException("Unexpected version exception on create"); } @@ -300,16 +297,28 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM changeable = (openMode != READ_ONLY); // check DB version and read name, languageName, languageVersion and languageMinorVersion - VersionException dbVersionExc = initializeDatabase(openMode); - - VersionException languageVersionExc = null; + VersionException dbVersionExc = initializeProgramInfo(openMode); + LanguageVersionException languageVersionExc = null; try { language = DefaultLanguageService.getLanguageService().getLanguage(languageID); - languageVersionExc = checkLanguageVersion(openMode); + languageVersionExc = + LanguageVersionException.check(language, languageVersion, languageMinorVersion); } catch (LanguageNotFoundException e) { - languageVersionExc = checkForLanguageChange(e, openMode); + languageVersionExc = + LanguageVersionException.checkForLanguageChange(e, languageID, languageVersion); + } + + if (languageVersionExc != null) { + languageUpgradeRequired = true; + languageUpgradeTranslator = languageVersionExc.getLanguageTranslator(); + if (languageUpgradeTranslator != null) { + // stub language needed to facilitate upgrade process + language = languageVersionExc.getOldLanguage(); + languageVersion = language.getVersion(); + languageMinorVersion = language.getMinorVersion(); + } } initCompilerSpec(); @@ -320,12 +329,9 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM if (dbVersionExc != null) { versionExc = dbVersionExc.combine(versionExc); } - if (languageVersionExc != null) { - languageUpgradeRequired = true; - if (openMode != UPGRADE) { - // Language upgrade required - versionExc = languageVersionExc.combine(versionExc); - } + if (languageVersionExc != null && openMode != UPGRADE) { + // Language upgrade required + versionExc = languageVersionExc.combine(versionExc); } if (versionExc != null) { @@ -410,106 +416,14 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM language.getLanguageDescription().getDescription() + " Not Found, using default: " + e); langSpec = language.getDefaultCompilerSpec(); - if (compilerSpec == null) { + if (langSpec == null) { throw e; } - compilerSpecID = compilerSpec.getCompilerSpecID(); + compilerSpecID = langSpec.getCompilerSpecID(); } compilerSpec = ProgramCompilerSpec.getProgramCompilerSpec(this, langSpec); } - /** - * Language corresponding to languageId was found. Check language version - * for language upgrade situation. - * @param openMode one of: - * READ_ONLY: the original database will not be modified - * UPDATE: the database can be written to. - * UPGRADE: the database is upgraded to the latest schema as it is opened. - * @throws LanguageNotFoundException if a language cannot be found for this program - * @return VersionException if language upgrade required - */ - private VersionException checkLanguageVersion(int openMode) throws LanguageNotFoundException { - - if (language.getVersion() > languageVersion) { - - Language newLanguage = language; - - Language oldLanguage = OldLanguageFactory.getOldLanguageFactory() - .getOldLanguage(languageID, languageVersion); - if (oldLanguage == null) { - // Assume minor version behavior - old language does not exist for current major version - Msg.error(this, "Old language specification not found: " + languageID + - " (Version " + languageVersion + ")"); - return new VersionException(true); - } - - // Ensure that we can upgrade the language - languageUpgradeTranslator = LanguageTranslatorFactory.getLanguageTranslatorFactory() - .getLanguageTranslator(oldLanguage, newLanguage); - if (languageUpgradeTranslator == null) { - -// TODO: This is a bad situation!! Most language revisions should be supportable, if not we have no choice but to throw -// a LanguageNotFoundException until we figure out how to deal with nasty translations which require -// a complete redisassembly and possibly auto analysis. - - throw new LanguageNotFoundException(language.getLanguageID(), - "(Ver " + languageVersion + "." + languageMinorVersion + " -> " + - newLanguage.getVersion() + "." + newLanguage.getMinorVersion() + - ") language version translation not supported"); - } - language = oldLanguage; - return new VersionException(true); - } - else if (language.getVersion() == languageVersion && - language.getMinorVersion() > languageMinorVersion) { - // Minor version change - translator not needed (languageUpgradeTranslator is null) - return new VersionException(true); - } - else if (language.getMinorVersion() != languageMinorVersion || - language.getVersion() != languageVersion) { - throw new LanguageNotFoundException(language.getLanguageID(), languageVersion, - languageMinorVersion); - } - return null; - } - - /** - * Language specified by languageName was not found. Check for - * valid language translation/migration. Old language version specified by - * languageVersion. - * @param openMode one of: - * READ_ONLY: the original database will not be modified - * UPDATE: the database can be written to. - * UPGRADE: the database is upgraded to the latest schema as it is opened. - * @return true if language upgrade required - * @throws LanguageNotFoundException if a suitable replacement language not found - */ - private VersionException checkForLanguageChange(LanguageNotFoundException e, int openMode) - throws LanguageNotFoundException { - - languageUpgradeTranslator = LanguageTranslatorFactory.getLanguageTranslatorFactory() - .getLanguageTranslator(languageID, languageVersion); - if (languageUpgradeTranslator == null) { - throw e; - } - - language = languageUpgradeTranslator.getOldLanguage(); - languageID = language.getLanguageID(); - - VersionException ve = new VersionException(true); - LanguageID oldLangName = languageUpgradeTranslator.getOldLanguage().getLanguageID(); - LanguageID newLangName = languageUpgradeTranslator.getNewLanguage().getLanguageID(); - String message; - if (oldLangName.equals(newLangName)) { - message = "Program requires a processor language version change"; - } - else { - message = "Program requires a processor language change to: " + newLangName; - } - ve.setDetailMessage(message); - return ve; - } - @Override protected void setDomainFile(DomainFile df) { super.setDomainFile(df); @@ -571,22 +485,22 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM } @Override - public SymbolTable getSymbolTable() { - return (SymbolTable) managers[SYMBOL_MGR]; + public SymbolManager getSymbolTable() { + return (SymbolManager) managers[SYMBOL_MGR]; } @Override - public ExternalManager getExternalManager() { - return (ExternalManager) managers[EXTERNAL_MGR]; + public ExternalManagerDB getExternalManager() { + return (ExternalManagerDB) managers[EXTERNAL_MGR]; } @Override - public EquateTable getEquateTable() { - return (EquateTable) managers[EQUATE_MGR]; + public EquateManager getEquateTable() { + return (EquateManager) managers[EQUATE_MGR]; } @Override - public Memory getMemory() { + public MemoryMapDB getMemory() { return memoryManager; } @@ -595,8 +509,8 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM } @Override - public ReferenceManager getReferenceManager() { - return (ReferenceManager) managers[REF_MGR]; + public ReferenceDBManager getReferenceManager() { + return (ReferenceDBManager) managers[REF_MGR]; } public CodeManager getCodeManager() { @@ -613,17 +527,17 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM } @Override - public FunctionManager getFunctionManager() { + public FunctionManagerDB getFunctionManager() { return (FunctionManagerDB) managers[FUNCTION_MGR]; } @Override - public BookmarkManager getBookmarkManager() { - return (BookmarkManager) managers[BOOKMARK_MGR]; + public BookmarkDBManager getBookmarkManager() { + return (BookmarkDBManager) managers[BOOKMARK_MGR]; } @Override - public RelocationTable getRelocationTable() { + public RelocationManager getRelocationTable() { return (RelocationManager) managers[RELOC_MGR]; } @@ -1183,9 +1097,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM if (name.equals(newName)) { return; } - DBRecord record = table.getRecord(new StringField(PROGRAM_NAME)); - record.setString(0, newName); - table.putRecord(record); + dataMap.put(PROGRAM_NAME, newName); getTreeManager().setProgramName(name, newName); super.setName(newName); } @@ -1198,8 +1110,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM } private void refreshName() throws IOException { - DBRecord record = table.getRecord(new StringField(PROGRAM_NAME)); - name = record.getString(0); + name = dataMap.get(PROGRAM_NAME); } private void refreshImageBase() throws IOException { @@ -1292,9 +1203,9 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM } private long getStoredBaseImageOffset() throws IOException { - DBRecord rec = table.getRecord(new StringField(IMAGE_OFFSET)); - if (rec != null) { - return (new BigInteger(rec.getString(0), 16)).longValue(); + String imageBaseStr = dataMap.get(IMAGE_OFFSET); + if (imageBaseStr != null) { + return (new BigInteger(imageBaseStr, 16)).longValue(); } return 0; } @@ -1352,10 +1263,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM if (commit) { try { - DBRecord record = SCHEMA.createRecord(new StringField(IMAGE_OFFSET)); - record.setString(0, Long.toHexString(base.getOffset())); - table.putRecord(record); - + dataMap.put(IMAGE_OFFSET, Long.toHexString(base.getOffset())); storedImageBase = base; imageBaseOverride = false; @@ -1409,32 +1317,15 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM return "Program"; } - private void createDatabase() throws IOException { - table = dbh.createTable(TABLE_NAME, SCHEMA); - DBRecord record = SCHEMA.createRecord(new StringField(PROGRAM_NAME)); - record.setString(0, name); - table.putRecord(record); - + private void createProgramInfo() throws IOException { + dataMap = new DBStringMapAdapter(dbh, DATA_MAP_TABLE_NAME, true); + dataMap.put(PROGRAM_NAME, name); // NOTE: Keep unused language name record for backward compatibility to avoid NPE - record = SCHEMA.createRecord(new StringField(OLD_LANGUAGE_NAME)); - record.setString(0, languageID.getIdAsString()); - table.putRecord(record); - - record = SCHEMA.createRecord(new StringField(LANGUAGE_ID)); - record.setString(0, languageID.getIdAsString()); - table.putRecord(record); - - record = SCHEMA.createRecord(new StringField(COMPILER_SPEC_ID)); - record.setString(0, compilerSpecID.getIdAsString()); - table.putRecord(record); - - record = SCHEMA.createRecord(new StringField(LANGUAGE_VERSION)); - record.setString(0, languageVersion + "." + languageMinorVersion); - table.putRecord(record); - - record = SCHEMA.createRecord(new StringField(PROGRAM_DB_VERSION)); - record.setString(0, Integer.toString(DB_VERSION)); - table.putRecord(record); + dataMap.put(OLD_LANGUAGE_NAME, languageID.getIdAsString()); + dataMap.put(LANGUAGE_ID, languageID.getIdAsString()); + dataMap.put(COMPILER_SPEC_ID, compilerSpecID.getIdAsString()); + dataMap.put(LANGUAGE_VERSION, languageVersion + "." + languageMinorVersion); + dataMap.put(PROGRAM_DB_VERSION, Integer.toString(DB_VERSION)); } /** @@ -1451,21 +1342,16 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM * @throws VersionException if the data is newer than this version of Ghidra and can not be * upgraded or opened. */ - private VersionException initializeDatabase(int openMode) + private VersionException initializeProgramInfo(int openMode) throws IOException, VersionException, LanguageNotFoundException { boolean requiresUpgrade = false; - table = dbh.getTable(TABLE_NAME); - if (table == null) { - throw new IOException("Unsupported File Content"); - } - DBRecord record = table.getRecord(new StringField(PROGRAM_NAME)); - name = record.getString(0); + dataMap = new DBStringMapAdapter(dbh, DATA_MAP_TABLE_NAME, false); + name = dataMap.get(PROGRAM_NAME); - record = table.getRecord(new StringField(LANGUAGE_ID)); - if (record == null) { // must be in old style combined language/compiler spec format - record = table.getRecord(new StringField(OLD_LANGUAGE_NAME)); - String oldLanguageName = record.getString(0); + String languageIdStr = dataMap.get(LANGUAGE_ID); + if (languageIdStr == null) { // must be in old style combined language/compiler spec format + String oldLanguageName = dataMap.get(OLD_LANGUAGE_NAME); LanguageCompilerSpecPair languageCompilerSpecPair = OldLanguageMappingService.lookupMagicString(oldLanguageName, false); if (languageCompilerSpecPair == null) { @@ -1477,31 +1363,28 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM requiresUpgrade = true; } else { - record = SCHEMA.createRecord(new StringField(LANGUAGE_ID)); - record.setString(0, languageID.getIdAsString()); - table.putRecord(record); - record = SCHEMA.createRecord(new StringField(COMPILER_SPEC_ID)); - record.setString(0, compilerSpecID.getIdAsString()); - table.putRecord(record); + dataMap.put(LANGUAGE_ID, languageID.getIdAsString()); + dataMap.put(COMPILER_SPEC_ID, compilerSpecID.getIdAsString()); } } else { - languageID = new LanguageID(record.getString(0)); - record = table.getRecord(new StringField(COMPILER_SPEC_ID)); - compilerSpecID = new CompilerSpecID(record.getString(0)); + languageID = new LanguageID(languageIdStr); + compilerSpecID = new CompilerSpecID(dataMap.get(COMPILER_SPEC_ID)); } - record = table.getRecord(new StringField(LANGUAGE_VERSION)); - String languageVersionStr = record == null ? LANG_DEFAULT_VERSION : record.getString(0); - String[] vs = languageVersionStr.split("\\."); languageVersion = 1; languageMinorVersion = 0; - try { - languageVersion = Integer.parseInt(vs[0]); - languageMinorVersion = Integer.parseInt(vs[1]); - } - catch (Exception e) { - // Ignore + + String languageVersionStr = dataMap.get(LANGUAGE_VERSION); + if (languageVersionStr != null) { + try { + String[] vs = languageVersionStr.split("\\."); + languageVersion = Integer.parseInt(vs[0]); + languageMinorVersion = Integer.parseInt(vs[1]); + } + catch (Exception e) { + // Ignore + } } int storedVersion = getStoredVersion(); @@ -1525,16 +1408,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM checkFunctionWrappedPointers(monitor); // Update stored database version - table = dbh.getTable(TABLE_NAME); - Field key = new StringField(PROGRAM_DB_VERSION); - String versionStr = Integer.toString(DB_VERSION); - DBRecord record = table.getRecord(key); - if (record != null && versionStr.equals(record.getString(0))) { - return; // already has correct version - } - record = SCHEMA.createRecord(key); - record.setString(0, versionStr); - table.putRecord(record); + dataMap.put(PROGRAM_DB_VERSION, Integer.toString(DB_VERSION)); } /* @@ -1547,7 +1421,7 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM // Implemented compound VariableStorage and return "parameter" // Added signature SourceType stored in function flags // Added support for dynamic return/param storage - ((FunctionManagerDB) getFunctionManager()).initSignatureSource(monitor); + getFunctionManager().initSignatureSource(monitor); } // versions prior to COMPOUND_VARIABLE_STORAGE_ADDED_VERSION did not support // dynamic storage so the following upgrade is unnecessary @@ -1555,17 +1429,16 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM // Implemented auto and forced-indirect parameters - // must eliminate fix __thiscall functions using dynamic storage // to eliminate default 'this' parameter - ((FunctionManagerDB) getFunctionManager()).removeExplicitThisParameters(monitor); + getFunctionManager().removeExplicitThisParameters(monitor); } } public int getStoredVersion() throws IOException { - DBRecord record = table.getRecord(new StringField(PROGRAM_DB_VERSION)); - if (record != null) { - String s = record.getString(0); + String dbVersionDtr = dataMap.get(PROGRAM_DB_VERSION); + if (dbVersionDtr != null) { try { - return Integer.parseInt(s); + return Integer.parseInt(dbVersionDtr); } catch (NumberFormatException e) { // return 1 for invalid value @@ -1576,22 +1449,25 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM private void checkOldProperties(int openMode, TaskMonitor monitor) throws IOException, VersionException { - DBRecord record = table.getRecord(new StringField(EXECUTE_PATH)); - if (record != null) { + String exePath = dataMap.get(EXECUTE_PATH); + if (exePath != null) { if (openMode == READ_ONLY) { return; // not important, get on path or format will return "unknown" } if (openMode != UPGRADE) { throw new VersionException(true); } + + // migrate old data to program info Options pl = getOptions(PROGRAM_INFO); - String value = record.getString(0); - pl.setString(EXECUTABLE_PATH, value); - table.deleteRecord(record.getKeyField()); - record = table.getRecord(new StringField(EXECUTE_FORMAT)); - if (record != null) { - pl.setString(EXECUTABLE_FORMAT, value); - table.deleteRecord(record.getKeyField()); + + pl.setString(EXECUTABLE_PATH, exePath); + dataMap.put(EXECUTE_PATH, null); + + String exeFormat = dataMap.get(EXECUTE_FORMAT); + if (exeFormat != null) { + pl.setString(EXECUTABLE_FORMAT, exeFormat); + dataMap.put(EXECUTE_FORMAT, null); } } int storedVersion = getStoredVersion(); @@ -1615,7 +1491,6 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM throw new VersionException(true); } } - } /* @@ -2103,9 +1978,9 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM addrMap.memoryMapChanged(memoryManager); monitor.setMessage("Updating symbols..."); - ((SymbolManager) getSymbolTable()).setLanguage(translator, monitor); - ((ExternalManagerDB) getExternalManager()).setLanguage(translator, monitor); - ((FunctionManagerDB) getFunctionManager()).setLanguage(translator, monitor); + getSymbolTable().setLanguage(translator, monitor); + getExternalManager().setLanguage(translator, monitor); + getFunctionManager().setLanguage(translator, monitor); } clearCache(true); @@ -2133,15 +2008,10 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM translator.fixupInstructions(this, translator.getOldLanguage(), monitor); } - DBRecord record = SCHEMA.createRecord(new StringField(LANGUAGE_ID)); - record.setString(0, languageID.getIdAsString()); - table.putRecord(record); - record = SCHEMA.createRecord(new StringField(COMPILER_SPEC_ID)); - record.setString(0, compilerSpecID.getIdAsString()); - table.putRecord(record); - record = SCHEMA.createRecord(new StringField(LANGUAGE_VERSION)); - record.setString(0, languageVersion + "." + languageMinorVersion); - table.putRecord(record); + dataMap.put(LANGUAGE_ID, languageID.getIdAsString()); + dataMap.put(COMPILER_SPEC_ID, compilerSpecID.getIdAsString()); + dataMap.put(LANGUAGE_VERSION, languageVersion + "." + languageMinorVersion); + setChanged(true); clearCache(true); invalidate(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeManager.java index b93d85e7a5..9626ac7c3a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeManager.java @@ -21,7 +21,8 @@ import java.util.*; import db.*; import db.util.ErrorHandler; import ghidra.program.database.*; -import ghidra.program.database.data.DataTypeManagerDB; +import ghidra.program.database.data.PointerTypedefInspector; +import ghidra.program.database.data.ProgramDataTypeManager; import ghidra.program.database.map.*; import ghidra.program.database.properties.*; import ghidra.program.database.symbol.SymbolManager; @@ -55,7 +56,7 @@ public class CodeManager implements ErrorHandler, ManagerDB { private ProgramDB program; private PrototypeManager protoMgr; private DBObjectCache cache; - private DataTypeManagerDB dataManager; + private ProgramDataTypeManager dataManager; private EquateTable equateTable; private SymbolManager symbolTable; private ProgramContext contextMgr; @@ -2044,7 +2045,7 @@ public class CodeManager implements ErrorHandler, ManagerDB { if (block == null || !block.isInitialized()) { return; } - DataType dt = data.getBaseDataType(); + DataType dt = data.getDataType(); if (Address.class.equals(dt.getValueClass(null))) { Object obj = data.getValue(); if (obj instanceof Address) { @@ -2100,7 +2101,7 @@ public class CodeManager implements ErrorHandler, ManagerDB { return; // treat 0 and all f's as uninitialized pointer value } - // for 64 bit programs, make sure we are creating pointers on random bytes which would + // for 64 bit programs, make sure we are not creating pointers on random bytes which would // pollute our 32 bit segment map and make Ghidra run poorly. if (toAddr.getAddressSpace().getSize() > 32) { if (exceedsLimitOn64BitAddressSegments(longSegmentAddressList, toAddr)) { @@ -2108,7 +2109,20 @@ public class CodeManager implements ErrorHandler, ManagerDB { } } - addDataReference(data.getMinAddress(), toAddr, true); + DataType dataType = data.getDataType(); + if (dataType instanceof TypeDef) { + // Check for pointer-typedef with component offset setting + long pointerComponentOffset = + PointerTypedefInspector.getPointerComponentOffset((TypeDef) dataType); + if (pointerComponentOffset != 0) { + refManager.addOffsetMemReference(data.getMinAddress(), toAddr, + pointerComponentOffset, RefType.DATA, SourceType.DEFAULT, 0); + return; + } + } + + refManager.addMemoryReference(data.getMinAddress(), toAddr, RefType.DATA, + SourceType.DEFAULT, 0); } private boolean exceedsLimitOn64BitAddressSegments(List

    longSegmentAddressList, @@ -2127,15 +2141,6 @@ public class CodeManager implements ErrorHandler, ManagerDB { return true; } - private boolean addDataReference(Address fromAddr, Address toAddr, boolean isPrimary) { - Reference ref = - refManager.addMemoryReference(fromAddr, toAddr, RefType.DATA, SourceType.DEFAULT, 0); - if (!isPrimary) { - refManager.setPrimary(ref, isPrimary); - } - return true; - } - /** * Clears all comments in the given range (inclusive). * diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeUnitDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeUnitDB.java index 0bbf30633d..0ca35a7e9c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeUnitDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/CodeUnitDB.java @@ -30,7 +30,6 @@ import ghidra.program.model.listing.*; import ghidra.program.model.mem.*; import ghidra.program.model.symbol.*; import ghidra.program.model.util.*; -import ghidra.program.util.ChangeManager; import ghidra.util.*; import ghidra.util.exception.*; import ghidra.util.prop.PropertyVisitor; @@ -57,7 +56,6 @@ abstract class CodeUnitDB extends DatabaseObject implements CodeUnit, ProcessorC private boolean checkedComments; protected byte[] bytes; private ProgramContext programContext; - protected ChangeManager changeMgr; protected Lock lock; /** @@ -80,7 +78,6 @@ abstract class CodeUnitDB extends DatabaseObject implements CodeUnit, ProcessorC program = (ProgramDB) codeMgr.getProgram(); refMgr = program.getReferenceManager(); programContext = program.getProgramContext(); - changeMgr = program; } @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/DataComponent.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/DataComponent.java index 67634eec4d..95132f8897 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/DataComponent.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/DataComponent.java @@ -16,6 +16,7 @@ package ghidra.program.database.code; import db.DBRecord; +import ghidra.docking.settings.Settings; import ghidra.program.database.DBObjectCache; import ghidra.program.model.address.Address; import ghidra.program.model.data.*; @@ -57,30 +58,28 @@ class DataComponent extends DataDB { this.level = parent.level + 1; this.offset = component.getOffset(); this.length = component.getLength(); - defaultSettings = component.getDefaultSettings(); } /** - * Constructs a new DataComponent + * Constructs a new array DataComponent. * @param codeMgr the code manager. * @param componentCache data component cache * @param address the address of the data component * @param addr the convert address long value * @param parent the DataDB object that contains this component. - * @param dt the datatype for this component. + * @param array the array containing this component. * @param ordinal the ordinal for this component. * @param offset the offset of this component within its parent. - * @param the length of this component. + * @param length the length of this component. */ DataComponent(CodeManager codeMgr, DBObjectCache componentCache, Address address, - long addr, DataDB parent, DataType dt, int ordinal, int offset, int length) { - super(codeMgr, componentCache, ordinal, address, addr, dt); + long addr, DataDB parent, Array array, int ordinal, int offset, int length) { + super(codeMgr, componentCache, ordinal, address, addr, array.getDataType()); this.indexInParent = ordinal; this.parent = parent; this.offset = offset; this.level = parent.level + 1; this.length = length; - defaultSettings = dataType.getDefaultSettings(); } @Override @@ -120,20 +119,10 @@ class DataComponent extends DataDB { address = parent.getAddress().add(offset); addr = parent.addr + offset; baseDataType = getBaseDataType(dataType); - if (component != null) { - defaultSettings = component.getDefaultSettings(); - } - else { - defaultSettings = dataType.getDefaultSettings(); - } bytes = null; return false; } - /** - * - * @see ghidra.program.model.listing.Data#getComponentPath() - */ @Override public int[] getComponentPath() { if (path == null) { @@ -151,12 +140,6 @@ class DataComponent extends DataDB { return path; } - /** - * Get the name of this Data that is a component of another - * Data Item. - * @return the name as a component of another prototype, - * and null if this is not a component of another prototype. - */ @Override public String getFieldName() { if (component == null) { // is array? @@ -169,18 +152,12 @@ class DataComponent extends DataDB { return myName; } - /** - * Returns the path name (dot notation) for this field - */ @Override public String getPathName() { String parentPath = parent.getPathName(); return getComponentName(parentPath); } - /** - * Returns the relative path name (dot notation) for this field - */ @Override public String getComponentPathName() { String parentPath = parent.getComponentPathName(); @@ -200,55 +177,31 @@ class DataComponent extends DataDB { return stringBuffer.toString(); } - /** - * Get the immediate parent Data Prototype of this component - */ @Override public Data getParent() { return parent; } - /** - * Get the highest level Data Prototype in a hierarchy of structures - * containing this component. - */ @Override public Data getRoot() { return parent.getRoot(); } - /** - * Get the offset of this Data item from the start of - * some hierarchy of structures. - */ @Override public int getRootOffset() { return parent.getRootOffset() + getParentOffset(); } - /** - * Get the offset of this Data item from the start of its immediate - * parent. - */ @Override public int getParentOffset() { return offset; } - /** - * Get the index of this Data item within its parent - * - * @return the index of this component in its parent - * returns -1 if this is not a component - */ @Override public int getComponentIndex() { return indexInParent; } - /** - * @see java.lang.Object#equals(java.lang.Object) - */ @Override public boolean equals(Object obj) { @@ -313,9 +266,6 @@ class DataComponent extends DataDB { } } - /** - * @see ghidra.program.model.listing.CodeUnit#getComment(int) - */ @Override public String getComment(int commentType) { String cmt = super.getComment(commentType); @@ -325,6 +275,14 @@ class DataComponent extends DataDB { return cmt; } + @Override + public Settings getDefaultSettings() { + if (component != null) { + return component.getDefaultSettings(); + } + return super.getDefaultSettings(); + } + @Override protected Address getDataSettingsAddress() { if (parent.getBaseDataType() instanceof Array) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/DataDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/DataDB.java index 6c28bc0a40..d9cf9cff29 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/DataDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/code/DataDB.java @@ -21,7 +21,7 @@ import db.DBRecord; import ghidra.docking.settings.Settings; import ghidra.docking.settings.SettingsDefinition; import ghidra.program.database.DBObjectCache; -import ghidra.program.database.data.DataTypeManagerDB; +import ghidra.program.database.data.ProgramDataTypeManager; import ghidra.program.database.map.AddressMap; import ghidra.program.model.address.*; import ghidra.program.model.data.*; @@ -30,7 +30,6 @@ import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.scalar.Scalar; import ghidra.program.model.symbol.*; -import ghidra.program.util.ChangeManager; import ghidra.util.Msg; /** @@ -48,8 +47,7 @@ class DataDB extends CodeUnitDB implements Data { protected DataType baseDataType; protected int level = 0; - protected DataTypeManagerDB dataMgr; - protected Settings defaultSettings; + protected ProgramDataTypeManager dataMgr; private Boolean hasMutabilitySetting; @@ -70,7 +68,6 @@ class DataDB extends CodeUnitDB implements Data { baseDataType = getBaseDataType(dataType); - defaultSettings = dataType.getDefaultSettings(); length = -1; // lazy compute } @@ -117,7 +114,6 @@ class DataDB extends CodeUnitDB implements Data { } dataType = dt; baseDataType = getBaseDataType(dataType); - defaultSettings = dataType.getDefaultSettings(); length = -1; // set to compute lazily later bytes = null; return false; @@ -142,13 +138,8 @@ class DataDB extends CodeUnitDB implements Data { if (length < 1) { length = codeMgr.getLength(address); } - if (length < 1) { - if (baseDataType instanceof Pointer) { - length = address.getPointerSize(); - } - else { - length = 1; - } + if (length <= 0) { + length = 1; } // no need to do all that follow on checking when length == 1 @@ -242,7 +233,7 @@ class DataDB extends CodeUnitDB implements Data { int elementLength = array.getElementLength(); Address componentAddr = address.add(index * elementLength); return new DataComponent(codeMgr, componentCache, componentAddr, - addressMap.getKey(componentAddr, false), this, array.getDataType(), index, + addressMap.getKey(componentAddr, false), this, array, index, index * elementLength, elementLength); } if (baseDataType instanceof Composite) { @@ -280,10 +271,6 @@ class DataDB extends CodeUnitDB implements Data { return null; } - /** - * Provide default formatted string representation of this instruction. - * @see java.lang.Object#toString() - */ @Override public String toString() { String valueRepresentation = getDefaultValueRepresentation(); @@ -346,8 +333,7 @@ class DataDB extends CodeUnitDB implements Data { private T getSettingsDefinition( Class settingsDefinitionClass) { - DataType dt = baseDataType; - for (SettingsDefinition def : dt.getSettingsDefinitions()) { + for (SettingsDefinition def : dataType.getSettingsDefinitions()) { if (settingsDefinitionClass.isAssignableFrom(def.getClass())) { return settingsDefinitionClass.cast(def); } @@ -391,28 +377,15 @@ class DataDB extends CodeUnitDB implements Data { public void clearSetting(String name) { refreshIfNeeded(); Address cuAddr = getDataSettingsAddress(); - if (dataMgr.clearSetting(cuAddr, name)) { - changeMgr.setChanged(ChangeManager.DOCR_DATA_TYPE_SETTING_CHANGED, cuAddr, cuAddr, null, - null); - } - } - - @Override - public byte[] getByteArray(String name) { - refreshIfNeeded(); - byte[] tempBytes = dataMgr.getByteSettingsValue(getDataSettingsAddress(), name); - if (tempBytes == null && defaultSettings != null) { - tempBytes = defaultSettings.getByteArray(name); - } - return tempBytes; + dataMgr.clearSetting(cuAddr, name); } @Override public Long getLong(String name) { refreshIfNeeded(); Long value = dataMgr.getLongSettingsValue(getDataSettingsAddress(), name); - if (value == null && defaultSettings != null) { - value = defaultSettings.getLong(name); + if (value == null) { + value = getDefaultSettings().getLong(name); } return value; } @@ -420,15 +393,15 @@ class DataDB extends CodeUnitDB implements Data { @Override public String[] getNames() { refreshIfNeeded(); - return dataMgr.getNames(getDataSettingsAddress()); + return dataMgr.getInstanceSettingsNames(getDataSettingsAddress()); } @Override public String getString(String name) { refreshIfNeeded(); String value = dataMgr.getStringSettingsValue(getDataSettingsAddress(), name); - if (value == null && defaultSettings != null) { - value = defaultSettings.getString(name); + if (value == null) { + value = getDefaultSettings().getString(name); } return value; } @@ -437,50 +410,31 @@ class DataDB extends CodeUnitDB implements Data { public Object getValue(String name) { refreshIfNeeded(); Object value = dataMgr.getSettings(getDataSettingsAddress(), name); - if (value == null && defaultSettings != null) { - value = defaultSettings.getValue(name); + if (value == null) { + value = getDefaultSettings().getValue(name); } return value; } - @Override - public void setByteArray(String name, byte[] value) { - refreshIfNeeded(); - Address cuAddr = getDataSettingsAddress(); - if (dataMgr.setByteSettingsValue(cuAddr, name, value)) { - changeMgr.setChanged(ChangeManager.DOCR_DATA_TYPE_SETTING_CHANGED, cuAddr, cuAddr, null, - null); - } - } - @Override public void setLong(String name, long value) { refreshIfNeeded(); Address cuAddr = getDataSettingsAddress(); - if (dataMgr.setLongSettingsValue(cuAddr, name, value)) { - changeMgr.setChanged(ChangeManager.DOCR_DATA_TYPE_SETTING_CHANGED, cuAddr, cuAddr, null, - null); - } + dataMgr.setLongSettingsValue(cuAddr, name, value); } @Override public void setString(String name, String value) { refreshIfNeeded(); Address cuAddr = getDataSettingsAddress(); - if (dataMgr.setStringSettingsValue(cuAddr, name, value)) { - changeMgr.setChanged(ChangeManager.DOCR_DATA_TYPE_SETTING_CHANGED, cuAddr, cuAddr, null, - null); - } + dataMgr.setStringSettingsValue(cuAddr, name, value); } @Override public void setValue(String name, Object value) { refreshIfNeeded(); Address cuAddr = getDataSettingsAddress(); - if (dataMgr.setSettings(cuAddr, name, value)) { - changeMgr.setChanged(ChangeManager.DOCR_DATA_TYPE_SETTING_CHANGED, cuAddr, cuAddr, null, - null); - } + dataMgr.setSettings(cuAddr, name, value); } @Override @@ -520,7 +474,6 @@ class DataDB extends CodeUnitDB implements Data { } } - @Deprecated @Override public Data getComponentAt(int offset) { return getComponentContaining(offset); @@ -638,45 +591,6 @@ class DataDB extends CodeUnitDB implements Data { return null; } -// public Data[] getComponents() { -// lock.acquire(); -// try { -// checkIsValid(); -// if (length < dataType.getLength()) { -// return null; -// } -// Data[] retData = EMPTY_COMPONENTS; -// if (baseDataType instanceof Composite) { -// Composite composite = (Composite)baseDataType; -// int n = composite.getNumComponents(); -// retData = new Data[n]; -// for(int i=0;i builtInMap = new HashMap<>(); private HashMap builtIn2IdMap = new HashMap<>(); private DBObjectCache catCache = new DBObjectCache<>(50); - private SettingsCache settingsCache = new SettingsCache(); + private SettingsCache settingsCache = new SettingsCache<>(200); private List sortedDataTypes; private Map> enumValueMap; @@ -123,9 +138,11 @@ abstract public class DataTypeManagerDB implements DataTypeManager { private boolean isBulkRemoving; - Lock lock; + protected AddressMap addrMap; protected DataOrganization dataOrganization; + + protected final Lock lock; private static class ResolvePair implements Comparable { @@ -171,6 +188,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { */ protected DataTypeManagerDB(DataOrganization dataOrganization) { this.lock = new Lock("DataTypeManagerDB"); + this.errHandler = new DbErrorHandler(); this.dataOrganization = dataOrganization; try { @@ -199,12 +217,13 @@ abstract public class DataTypeManagerDB implements DataTypeManager { * * @param packedDBfile packed datatype archive file (i.e., *.gdt resource). * @param openMode open mode CREATE, READ_ONLY or UPDATE (see - * {@link DBConstants}) + * {@link DBConstants}). * @throws IOException a low-level IO error. This exception may also be thrown * when a version error occurs (cause is VersionException). */ protected DataTypeManagerDB(ResourceFile packedDBfile, int openMode) throws IOException { + this.errHandler = new DbErrorHandler(); lock = new Lock("DataTypeManagerDB"); File file = packedDBfile.getFile(false); @@ -303,17 +322,19 @@ abstract public class DataTypeManagerDB implements DataTypeManager { } /** - * Constructor + * Constructor for a database-backed DataTypeManagerDB extension. * * @param handle database handle - * @param addrMap map to convert addresses to longs and longs to addresses - * @param openMode mode to open the DataTypeManager in + * @param addrMap address map (may be null) + * @param openMode open mode CREATE, READ_ONLY or UPDATE (see + * {@link DBConstants}). * @param errHandler the error handler * @param lock database lock * @param monitor the current task monitor * @throws CancelledException if an upgrade is cancelled * @throws IOException if there is a problem reading the database - * @throws VersionException if any database handle's version doesn't match the expected version + * @throws VersionException if any database handle's version doesn't match the expected version. + * This exception will never be thrown in READ_ONLY mode. */ protected DataTypeManagerDB(DBHandle handle, AddressMap addrMap, int openMode, ErrorHandler errHandler, Lock lock, TaskMonitor monitor) @@ -353,6 +374,8 @@ abstract public class DataTypeManagerDB implements DataTypeManager { // possible. // + checkAndUpdateManagerVersion(openMode); + VersionException versionExc = null; try { builtinAdapter = BuiltinDBAdapter.getAdapter(dbHandle, openMode, monitor); @@ -404,22 +427,14 @@ abstract public class DataTypeManagerDB implements DataTypeManager { versionExc = e.combine(versionExc); } try { - settingsAdapter = SettingsDBAdapter.getAdapter(dbHandle, openMode, monitor); + settingsAdapter = SettingsDBAdapter.getAdapter(SETTINGS_TABLE_NAME, dbHandle, openMode, + null, monitor); } catch (VersionException e) { versionExc = e.combine(versionExc); } - if (addrMap != null) { - try { - instanceSettingsAdapter = - InstanceSettingsDBAdapter.getAdapter(dbHandle, openMode, addrMap, monitor); - } - catch (VersionException e) { - versionExc = e.combine(versionExc); - } - } try { - pointerAdapter = PointerDBAdapter.getAdapter(dbHandle, openMode, monitor, addrMap); + pointerAdapter = PointerDBAdapter.getAdapter(dbHandle, openMode, monitor); } catch (VersionException e) { versionExc = e.combine(versionExc); @@ -448,11 +463,32 @@ abstract public class DataTypeManagerDB implements DataTypeManager { catch (VersionException e) { versionExc = e.combine(versionExc); } + + try { + initializeOtherAdapters(openMode, monitor); + } + catch (VersionException e) { + versionExc = e.combine(versionExc); + } if (versionExc != null) { throw versionExc; } } + + /** + * Initialize other DB adapters after base implementation adapters has been + * initialized. + * @param openMode the DB open mode (see {@link DBConstants}) + * @param monitor the progress monitor + * @throws CancelledException if the user cancels an upgrade + * @throws VersionException if the database does not match the expected version. + * @throws IOException if a database IO error occurs. + */ + protected void initializeOtherAdapters(int openMode, TaskMonitor monitor) + throws CancelledException, IOException, VersionException { + // do nothing + } /** * Build Parent/Child table for tracking dataType usage by other dataTypes @@ -498,6 +534,160 @@ abstract public class DataTypeManagerDB implements DataTypeManager { } } + /** + * Check data map for overall manager version for compatibility. + * If not open read-only the map will be immediately updated to latest version. + * @throws VersionException if database is a newer unsupported version + * @throws IOException if an IO error occurs + */ + private void checkAndUpdateManagerVersion(int openMode) throws IOException, VersionException { + + if (openMode == DBConstants.CREATE) { + DBStringMapAdapter dataMap = getDataMap(true); + dataMap.put(DTM_DB_VERSION_KEY, Integer.toString(DB_VERSION)); + return; + } + + // Check data map for overall manager version for compatibility. + // If not open read-only the map will be immediately updated to latest version. + DBStringMapAdapter dataMap = getDataMap(openMode == DBConstants.UPGRADE); + if (dataMap != null) { + // verify that we are compatible with stored data + int dbVersion = dataMap.getInt(DTM_DB_VERSION_KEY, 1); + if (dbVersion > DB_VERSION) { + throw new VersionException(false); + } + if (dbVersion < DB_VERSION) { + if (openMode == DBConstants.UPGRADE) { + // Upgrade mode required to advance overall DB version + dataMap.put(DTM_DB_VERSION_KEY, Integer.toString(DB_VERSION)); + } + else if (openMode == DBConstants.UPDATE) { + throw new VersionException(true); + } + } + } + else if (openMode == DBConstants.UPDATE) { + // missing data map + throw new VersionException(true); + } + } + + /** + * Get the manager string data map. + * @param createIfNeeded if true map will be created if it does not exist + * @return manager string data map or null + * @throws IOException if an IO error occurs + */ + protected DBStringMapAdapter getDataMap(boolean createIfNeeded) throws IOException { + DBStringMapAdapter dataMap = null; + boolean exists = (dbHandle.getTable(MAP_TABLE_NAME) != null); + if (exists) { + dataMap = new DBStringMapAdapter(dbHandle, MAP_TABLE_NAME, false); + } + else if (createIfNeeded) { + dataMap = new DBStringMapAdapter(dbHandle, MAP_TABLE_NAME, true); + } + return dataMap; + } + + boolean clearSetting(long dataTypeId, String name) { + lock.acquire(); + try { + settingsCache.remove(dataTypeId, name); + return settingsAdapter.removeSettingsRecord(dataTypeId, name); + } + catch (IOException e) { + errHandler.dbError(e); + } + finally { + lock.release(); + } + return false; + } + + boolean clearAllSettings(long dataTypeId) { + lock.acquire(); + try { + boolean changed = false; + Field[] keys = settingsAdapter.getSettingsKeys(dataTypeId); + for (Field key : keys) { + long settingsId = key.getLongValue(); + DBRecord rec = settingsAdapter.getSettingsRecord(settingsId); + String name = settingsAdapter.getSettingName(rec); + settingsAdapter.removeSettingsRecord(settingsId); + settingsCache.remove(dataTypeId, name); + changed = true; + } + return changed; + } + catch (IOException e) { + errHandler.dbError(e); + } + finally { + lock.release(); + } + return false; + } + + String[] getSettingsNames(long dataTypeId) { + lock.acquire(); + try { + return settingsAdapter.getSettingsNames(dataTypeId); + } + catch (IOException e) { + errHandler.dbError(e); + } + finally { + lock.release(); + } + return new String[0]; + } + + SettingDB getSetting(long dataTypeId, String name) { + lock.acquire(); + try { + SettingDB setting = settingsCache.get(dataTypeId, name); + if (setting != null) { + return setting; + } + DBRecord rec = settingsAdapter.getSettingsRecord(dataTypeId, name); + if (rec != null) { + setting = new SettingDB(rec, settingsAdapter.getSettingName(rec)); + settingsCache.put(dataTypeId, name, setting); + return setting; + } + } + catch (IOException e) { + errHandler.dbError(e); + } + finally { + lock.release(); + } + return null; + } + + boolean updateSettingsRecord(long dataTypeId, String name, String strValue, long longValue) { + lock.acquire(); + try { + DBRecord rec = + settingsAdapter.updateSettingsRecord(dataTypeId, name, strValue, longValue); + if (rec != null) { + SettingDB setting = new SettingDB(rec, settingsAdapter.getSettingName(rec)); + settingsCache.put(dataTypeId, name, setting); + return true; + } + return false; + } + catch (IOException e) { + errHandler.dbError(e); + } + finally { + lock.release(); + } + return false; + } + /** * Determine if transaction is active. With proper lock established * this method may be useful for determining if a lazy record update @@ -769,6 +959,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { if (dataType == DataType.DEFAULT) { return dataType; } + if (dataType instanceof BitFieldDataType) { return resolveBitFieldDataType((BitFieldDataType) dataType, handler); } @@ -1830,46 +2021,47 @@ abstract public class DataTypeManagerDB implements DataTypeManager { dataTypeDeleted(dataTypeID, deletedDtPath); } - private void deleteDataTypeRecord(long dataID) { - int tableID = getTableID(dataID); + private void deleteDataTypeRecord(long dataTypeID) { + int tableID = getTableID(dataTypeID); try { DataType dt = null; switch (tableID) { case BUILT_IN: - boolean status = builtinAdapter.removeRecord(dataID); + boolean status = builtinAdapter.removeRecord(dataTypeID); if (status) { - dt = builtInMap.remove(dataID); + dt = builtInMap.remove(dataTypeID); builtIn2IdMap.remove(dt); } break; case COMPOSITE: - removeComponents(dataID); - status = compositeAdapter.removeRecord(dataID); + removeComponents(dataTypeID); + status = compositeAdapter.removeRecord(dataTypeID); break; case COMPONENT: - status = componentAdapter.removeRecord(dataID); + status = componentAdapter.removeRecord(dataTypeID); break; case TYPEDEF: - status = typedefAdapter.removeRecord(dataID); + status = typedefAdapter.removeRecord(dataTypeID); break; case ARRAY: - status = arrayAdapter.removeRecord(dataID); + status = arrayAdapter.removeRecord(dataTypeID); break; case POINTER: - status = pointerAdapter.removeRecord(dataID); + status = pointerAdapter.removeRecord(dataTypeID); break; case FUNCTION_DEF: - removeParameters(dataID); - status = functionDefAdapter.removeRecord(dataID); + removeParameters(dataTypeID); + status = functionDefAdapter.removeRecord(dataTypeID); break; case PARAMETER: - status = paramAdapter.removeRecord(dataID); + status = paramAdapter.removeRecord(dataTypeID); break; case ENUM: - status = enumAdapter.removeRecord(dataID); + status = enumAdapter.removeRecord(dataTypeID); break; } + settingsAdapter.removeAllSettingsRecords(dataTypeID); } catch (IOException e) { errHandler.dbError(e); @@ -2127,11 +2319,14 @@ abstract public class DataTypeManagerDB implements DataTypeManager { } } - dt = (BuiltInDataType) c.getDeclaredConstructor().newInstance(); - dt.setName(name); - dt.setCategoryPath(catPath); - dt = dt.clone(this); - dt.setDefaultSettings(new SettingsDBManager(this, dt, dataTypeID)); + BuiltInDataType bdt = (BuiltInDataType) c.getDeclaredConstructor().newInstance(); + bdt.setName(name); + bdt.setCategoryPath(catPath); + bdt = (BuiltInDataType) bdt.clone(this); + if (allowsDefaultBuiltInSettings() && bdt.getSettingsDefinitions().length != 0) { + bdt.setDefaultSettings(new DataTypeSettingsDB(this, bdt, dataTypeID)); + } + dt = bdt; } catch (Exception e) { Msg.error(this, e); @@ -2401,7 +2596,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { structDB.doReplaceWith(struct, false); structDB.setDescription(struct.getDescription()); -// structDB.notifySizeChanged(); + // doReplaceWith may have updated the last change time so set it back to what we want. structDB.setLastChangeTime(struct.getLastChangeTime()); @@ -2419,33 +2614,6 @@ abstract public class DataTypeManagerDB implements DataTypeManager { return dbHandle.isChanged(); } -// private int getExternalAlignment(Composite struct) { -// if (struct.isDefaultAligned()) { -// return CompositeDB.DEFAULT_ALIGNED; -// } -// else if (struct.isMachineAligned()) { -// return CompositeDB.MACHINE_ALIGNED; -// } -// else { -// int alignment = struct.getAlignment(); -// if (alignment <= 0) { -// return CompositeDB.DEFAULT_ALIGNED; -// } -// return alignment; -// } -// } - -// private int getInternalAlignment(Composite struct) { -// if (struct.isPackingEnabled()) { -// int packingValue = struct.getPackingValue(); -// if (packingValue == 0) { -// return CompositeDB.ALIGNED_NO_PACKING; -// } -// return packingValue; -// } -// return CompositeDB.UNALIGNED; -// } - private TypeDef createTypeDef(TypeDef typedef, String name, Category cat, long sourceArchiveIdValue, long universalIdValue) throws IOException { @@ -2456,8 +2624,14 @@ abstract public class DataTypeManagerDB implements DataTypeManager { DBRecord record = typedefAdapter.createRecord(getID(dataType), name, cat.getID(), sourceArchiveIdValue, universalIdValue, typedef.getLastChangeTime()); TypedefDB typedefDB = new TypedefDB(this, dtCache, typedefAdapter, record); - dataType.addParent(typedefDB); + // Copy TypeDef settings from original + DataTypeSettingsDB settings = (DataTypeSettingsDB) typedefDB.getDefaultSettings(); + boolean wasLocked = settings.setLock(false); + TypedefDataType.copyTypeDefSettings(typedef, typedefDB, false); + settings.setLock(wasLocked); + + dataType.addParent(typedefDB); return typedefDB; } @@ -2480,7 +2654,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { unionDB.doReplaceWith(union, false); unionDB.setDescription(union.getDescription()); -// unionDB.notifySizeChanged(); + // doReplaceWith updated the last change time so set it back to what we want. unionDB.setLastChangeTime(union.getLastChangeTime()); @@ -2966,6 +3140,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { root.setInvalid(); catCache.invalidate(); settingsCache.clear(); + settingsAdapter.invalidateNameCache(); sortedDataTypes = null; enumValueMap = null; fireInvalidated(); @@ -2989,448 +3164,20 @@ abstract public class DataTypeManagerDB implements DataTypeManager { } } - /** - * Set the long value for instance settings. - * - * @param dataAddr min address of data - * @param name settings name - * @param value value of setting - * @return true if the settings actually changed - */ - - public boolean setLongSettingsValue(Address dataAddr, String name, long value) { - - return updateInstanceSettings(dataAddr, name, null, value, null); - } - - /** - * Set the string value for instance settings. - * - * @param dataAddr min address of data - * @param name settings name - * @param value value of setting - * @return true if the settings actually changed - */ - public boolean setStringSettingsValue(Address dataAddr, String name, String value) { - return updateInstanceSettings(dataAddr, name, value, -1, null); - } - - /** - * Set the byte array value for instance settings. - * - * @param dataAddr min address of data ata - * @param name settings name - * @param byteValue byte array value of setting - * @return true if the settings actually changed - */ - public boolean setByteSettingsValue(Address dataAddr, String name, byte[] byteValue) { - return updateInstanceSettings(dataAddr, name, null, -1, byteValue); - } - - /** - * Set the Object settings. - * - * @param dataAddr min address of data - * @param name the name of the settings - * @param value the value for the settings, must be either a String, byte[] - * or Long - * @return true if the settings were updated - */ - public boolean setSettings(Address dataAddr, String name, Object value) { - if (value instanceof String) { - return updateInstanceSettings(dataAddr, name, (String) value, -1, null); - } - else if (value instanceof byte[]) { - return updateInstanceSettings(dataAddr, name, null, -1, (byte[]) value); - } - else if (isAllowedNumberType(value)) { - return updateInstanceSettings(dataAddr, name, null, ((Number) value).longValue(), null); - } - throw new IllegalArgumentException( - "Unsupportd Settings Value: " + (value == null ? "null" : value.getClass().getName())); - } - - private boolean isAllowedNumberType(Object value) { - if (value instanceof Long) { - return true; - } - if (value instanceof Integer) { - return true; - } - if (value instanceof Short) { - return true; - } - if (value instanceof Byte) { - return true; - } - return false; - } - - /** - * Get the long value for an instance setting. - * - * @param dataAddr min address of data - * @param name settings name - * @return null if the named setting was not found - */ - public Long getLongSettingsValue(Address dataAddr, String name) { - InstanceSettingsDB settings = getInstanceSettingsDB(dataAddr, name); - if (settings != null) { - return settings.getLongValue(); - } - return null; - } - - /** - * Get the String value for an instance setting. - * - * @param dataAddr min address of data - * @param name settings name - * @return null if the named setting was not found - */ - public String getStringSettingsValue(Address dataAddr, String name) { - InstanceSettingsDB settings = getInstanceSettingsDB(dataAddr, name); - if (settings != null) { - return settings.getStringValue(); - } - return null; - } - - /** - * Get the byte array value for an instance setting. - * - * @param dataAddr min address of data - * @param name settings name - * @return null if the named setting was not found - */ - public byte[] getByteSettingsValue(Address dataAddr, String name) { - - InstanceSettingsDB settings = getInstanceSettingsDB(dataAddr, name); - if (settings != null) { - return settings.getByteValue(); - } - return null; - } - - /** - * Gets the value of a settings as an object (either String, byte[], or Long). - * - * @param dataAddr the address of the data for this settings - * @param name the name of settings. - * @return the settings object - */ - public Object getSettings(Address dataAddr, String name) { - Object obj = getStringSettingsValue(dataAddr, name); - if (obj != null) { - return obj; - } - obj = getByteSettingsValue(dataAddr, name); - if (obj != null) { - return obj; - } - return getLongSettingsValue(dataAddr, name); - } - - /** - * Clear the setting - * - * @param dataAddr min address of data - * @param name settings name - * @return true if the settings were cleared - */ - public boolean clearSetting(Address dataAddr, String name) { - if (instanceSettingsAdapter == null) { - throw new UnsupportedOperationException(); - } - lock.acquire(); - try { - InstanceSettingsDB settings = getInstanceSettingsDB(dataAddr, name); - if (settings != null) { - long key = settings.getKey(); - settingsCache.remove(dataAddr, name); - instanceSettingsAdapter.removeInstanceRecord(key); - return true; - } - } - catch (IOException e) { - errHandler.dbError(e); - - } - finally { - lock.release(); - } - return false; - } - - /** - * Clear all settings at the given address. - * - * @param dataAddr the address for this settings. - */ - public void clearAllSettings(Address dataAddr) { - if (instanceSettingsAdapter == null) { - throw new UnsupportedOperationException(); - } - lock.acquire(); - try { - settingsCache.clear(); - Field[] keys = instanceSettingsAdapter.getInstanceKeys(addrMap.getKey(dataAddr, false)); - for (Field key : keys) { - instanceSettingsAdapter.removeInstanceRecord(key.getLongValue()); - } - } - catch (IOException e) { - errHandler.dbError(e); - - } - finally { - lock.release(); - } - } - - /** - * Clears all settings in the given address range. - * - * @param start the first address of the range to clear - * @param end the last address of the range to clear. - * @param monitor the progress monitor for this operation. - * @throws CancelledException if the user cancels the operation. - */ - public void clearSettings(Address start, Address end, TaskMonitor monitor) - throws CancelledException { - if (instanceSettingsAdapter == null) { - throw new UnsupportedOperationException(); - } - lock.acquire(); - try { - settingsCache.clear(); - List keyRanges = addrMap.getKeyRanges(start, end, false); - for (KeyRange range : keyRanges) { - RecordIterator iter = - instanceSettingsAdapter.getRecords(range.minKey, range.maxKey); - while (iter.hasNext()) { - if (monitor.isCancelled()) { - throw new CancelledException(); - } - iter.next(); - iter.delete(); - } - } - } - catch (IOException e) { - errHandler.dbError(e); - } - finally { - lock.release(); - } - } - - /** - * Move the settings in the range to the new start address - * - * @param fromAddr start address from where to move - * @param toAddr new Address to move to - * @param length number of addresses to move - * @param monitor progress monitor - * @throws CancelledException if the operation was cancelled - */ - public void moveAddressRange(Address fromAddr, Address toAddr, long length, TaskMonitor monitor) - throws CancelledException { - if (instanceSettingsAdapter == null) { - throw new UnsupportedOperationException(); - } - - DBHandle scratchPad = null; - lock.acquire(); - try { - settingsCache.clear(); - scratchPad = dbHandle.getScratchPad(); - Table tmpTable = scratchPad.createTable(InstanceSettingsDBAdapter.INSTANCE_TABLE_NAME, - InstanceSettingsDBAdapterV0.V0_INSTANCE_SCHEMA); - - List keyRanges = - addrMap.getKeyRanges(fromAddr, fromAddr.add(length - 1), false); - for (KeyRange range : keyRanges) { - RecordIterator iter = - instanceSettingsAdapter.getRecords(range.minKey, range.maxKey); - while (iter.hasNext()) { - monitor.checkCanceled(); - DBRecord rec = iter.next(); - tmpTable.putRecord(rec); - iter.delete(); - } - } - - RecordIterator iter = tmpTable.iterator(); - while (iter.hasNext()) { - monitor.checkCanceled(); - DBRecord rec = iter.next(); - // update address column and re-introduce into table - Address addr = addrMap.decodeAddress( - rec.getLongValue(InstanceSettingsDBAdapter.INST_ADDR_COL)); - long offset = addr.subtract(fromAddr); - addr = toAddr.add(offset); - rec.setLongValue(InstanceSettingsDBAdapter.INST_ADDR_COL, - addrMap.getKey(addr, true)); - instanceSettingsAdapter.updateInstanceRecord(rec); - } - - } - catch (IOException e) { - errHandler.dbError(e); - } - finally { - if (scratchPad != null) { - try { - scratchPad.deleteTable(InstanceSettingsDBAdapter.INSTANCE_TABLE_NAME); - } - catch (IOException e) { - // ignore - } - } - lock.release(); - } - } - @Override public boolean isUpdatable() { return dbHandle.canUpdate(); } - /** - * Returns all the Settings names for the given address - * - * @param dataAddr the address - * @return the names - */ - public String[] getNames(Address dataAddr) { - if (instanceSettingsAdapter == null) { - throw new UnsupportedOperationException(); - } - lock.acquire(); - try { - Field[] keys = instanceSettingsAdapter.getInstanceKeys(addrMap.getKey(dataAddr, false)); - ArrayList list = new ArrayList<>(); - for (Field key : keys) { - DBRecord rec = instanceSettingsAdapter.getInstanceRecord(key.getLongValue()); - list.add(rec.getString(InstanceSettingsDBAdapter.INST_NAME_COL)); - } - String[] names = new String[list.size()]; - return list.toArray(names); - } - catch (IOException e) { - errHandler.dbError(e); - } - finally { - lock.release(); - } - return null; + @Override + public boolean allowsDefaultBuiltInSettings() { + return false; } - /** - * Returns true if no settings are set for the given address - * - * @param dataAddr the address to test - * @return true if not settings - */ - public boolean isEmptySetting(Address dataAddr) { - if (instanceSettingsAdapter == null) { - throw new UnsupportedOperationException(); - } - try { - return instanceSettingsAdapter.getInstanceKeys( - addrMap.getKey(dataAddr, false)).length == 0; - } - catch (IOException e) { - errHandler.dbError(e); - } - return true; - } - - private boolean updateInstanceSettings(Address dataAddr, String name, String strValue, - long longValue, byte[] byteValue) { - - boolean wasChanged = false; - - lock.acquire(); - try { - if (instanceSettingsAdapter == null) { - throw new UnsupportedOperationException(); - } - - InstanceSettingsDB settings = getInstanceSettingsDB(dataAddr, name); - if (settings == null) { - wasChanged = true; - // create new record - - DBRecord rec = instanceSettingsAdapter.createInstanceRecord( - addrMap.getKey(dataAddr, true), name, strValue, longValue, byteValue); - settings = new InstanceSettingsDB(rec); - settingsCache.put(dataAddr, name, settings); - } - else { - DBRecord rec = settings.getRecord(); - String recStrValue = rec.getString(SettingsDBAdapter.SETTINGS_STRING_VALUE_COL); - byte[] recByteValue = rec.getBinaryData(SettingsDBAdapter.SETTINGS_BYTE_VALUE_COL); - long recLongValue = rec.getLongValue(SettingsDBAdapter.SETTINGS_LONG_VALUE_COL); - wasChanged = SettingsDBManager.valuesChanged(recStrValue, strValue, byteValue, - recByteValue, recLongValue, longValue); - if (wasChanged) { - rec.setString(InstanceSettingsDBAdapter.INST_STRING_VALUE_COL, strValue); - rec.setLongValue(InstanceSettingsDBAdapter.INST_LONG_VALUE_COL, longValue); - rec.setBinaryData(InstanceSettingsDBAdapter.INST_BYTE_VALUE_COL, byteValue); - instanceSettingsAdapter.updateInstanceRecord(rec); - } - } - } - catch (IOException e) { - errHandler.dbError(e); - } - finally { - lock.release(); - } - - return wasChanged; - } - - private InstanceSettingsDB getInstanceSettingsDB(Address dataAddr, String name) { - lock.acquire(); - try { - if (instanceSettingsAdapter == null) { - throw new UnsupportedOperationException(); - } - InstanceSettingsDB settings = settingsCache.getInstanceSettings(dataAddr, name); - if (settings != null) { - return settings; - } - long addr = addrMap.getKey(dataAddr, false); - DBRecord rec = getInstanceRecord(addr, name); - if (rec != null) { - settings = new InstanceSettingsDB(rec); - settingsCache.put(dataAddr, name, settings); - return settings; - } - return null; - } - finally { - lock.release(); - } - } - - private DBRecord getInstanceRecord(long addr, String name) { - try { - Field[] keys = instanceSettingsAdapter.getInstanceKeys(addr); - for (Field key : keys) { - DBRecord rec = instanceSettingsAdapter.getInstanceRecord(key.getLongValue()); - if (rec.getString(InstanceSettingsDBAdapter.INST_NAME_COL).equals(name)) { - return rec; - } - } - } - catch (IOException e) { - errHandler.dbError(e); - } - return null; + @Override + public final boolean allowsDefaultComponentSettings() { + // default component settings support follows the same rules as BuiltIn settings + return allowsDefaultBuiltInSettings(); } /** @@ -3569,7 +3316,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { @Override public Pointer getPointer(DataType dt) { - return new PointerDataType(dt, -1, this); + return new PointerDataType(dt, this); } @Override @@ -3577,37 +3324,6 @@ abstract public class DataTypeManagerDB implements DataTypeManager { return new PointerDataType(dt, size, this); } - /** - * Removes all settings in the range - * - * @param startAddr the first address in the range. - * @param endAddr the last address in the range. - * @param monitor the progress monitor - * @throws CancelledException if the user cancelled the operation. - */ - public void deleteAddressRange(Address startAddr, Address endAddr, TaskMonitor monitor) - throws CancelledException { - if (instanceSettingsAdapter == null) { - throw new UnsupportedOperationException(); - } - lock.acquire(); - try { - List addrKeyRanges = addrMap.getKeyRanges(startAddr, endAddr, false); - int cnt = addrKeyRanges.size(); - for (int i = 0; i < cnt; i++) { - KeyRange kr = (KeyRange) addrKeyRanges.get(i); - instanceSettingsAdapter.delete(kr.minKey, kr.maxKey, monitor); - } - } - catch (IOException e) { - dbError(e); - } - finally { - settingsCache.clear(); - lock.release(); - } - } - @Override public void addDataTypeManagerListener(DataTypeManagerChangeListener l) { defaultListener.addDataTypeManagerListener(l); @@ -4294,42 +4010,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager { Msg.showError(this, null, "IO ERROR", message, e); } } -} -/** - * Cached object for the instance settings. - */ -class InstanceSettingsDB { - - private DBRecord record; - - InstanceSettingsDB(DBRecord record) { - this.record = record; - } - - public long getKey() { - return record.getKey(); - } - - byte[] getByteValue() { - return record.getBinaryData(InstanceSettingsDBAdapter.INST_BYTE_VALUE_COL); - } - - String getStringValue() { - return record.getString(InstanceSettingsDBAdapter.INST_STRING_VALUE_COL); - } - - Long getLongValue() { - return record.getLongValue(InstanceSettingsDBAdapter.INST_LONG_VALUE_COL); - } - - DBRecord getRecord() { - return record; - } - - protected boolean refresh() { - return false; - } } class CategoryCache extends FixedSizeHashMap { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeSettingsDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeSettingsDB.java new file mode 100644 index 0000000000..4208d0118f --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeSettingsDB.java @@ -0,0 +1,221 @@ +/* ### + * 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.program.database.data; + +import com.google.common.base.Predicate; + +import ghidra.docking.settings.Settings; +import ghidra.docking.settings.SettingsImpl; +import ghidra.program.model.data.*; +import ghidra.util.Msg; + +/** + * Default {@link Settings} handler for those datatypes managed + * by an associated {@link DataTypeManagerDB}. + */ +class DataTypeSettingsDB implements Settings { + + private final DataTypeManagerDB dataMgr; + private final long dataTypeID; + private final DataType dataType; + + private boolean locked; + private Predicate allowedSettingPredicate; + + private Settings defaultSettings; + + /** + * Constructor for settings storage manager. + * Initial state is locked for non-ProgramBasedDataTypeManager. + * @param dataMgr data type manager + * @param dataType built-in datatype + * @param dataTypeID resolved datatype ID + */ + DataTypeSettingsDB(DataTypeManagerDB dataMgr, BuiltInDataType dataType, long dataTypeID) { + this.dataMgr = dataMgr; + this.dataType = dataType; + this.dataTypeID = dataTypeID; + this.locked = !(dataMgr instanceof ProgramBasedDataTypeManager); + } + + /** + * Constructor for settings storage manager. + * Initial state is locked for non-ProgramBasedDataTypeManager. + * @param dataMgr data type manager + * @param dataType DB datatype + * @param dataTypeID resolved datatype ID + */ + DataTypeSettingsDB(DataTypeManagerDB dataMgr, DataTypeDB dataType, long dataTypeID) { + this.dataMgr = dataMgr; + this.dataType = dataType; + this.dataTypeID = dataTypeID; + this.locked = !(dataMgr instanceof ProgramBasedDataTypeManager); + } + + /** + * Change the current settings lock. Attempts to modify locked + * settings will be ignored with a logged error. This is done + * to write-protect settings at the public API level. + * @param lock true to lock, false to unlock + * @return previous lock state + */ + boolean setLock(boolean lock) { + boolean wasLocked = locked; + locked = lock; + return wasLocked; + } + + /** + * Set predicate for settings modification + * @param allowedSettingPredicate callback for checking an allowed setting modification + */ + void setAllowedSettingPredicate(Predicate allowedSettingPredicate) { + this.allowedSettingPredicate = allowedSettingPredicate; + } + + /** + * Check for immutable settings and log error of modification not permitted + * @param type setting type or null + * @param name setting name or null + * @return true if change permitted + */ + private boolean checkSetting(String type, String name) { + if (locked) { + String typeStr = ""; + if (type != null) { + typeStr = type + " "; + } + String nameStr = ": " + name; + if (name == null) { + nameStr = "s"; + } + Msg.warn(SettingsImpl.class, + "Ignored invalid attempt to modify immutable " + typeStr + "component setting" + + nameStr); + return false; + } + if (allowedSettingPredicate != null && !allowedSettingPredicate.apply(name)) { + Msg.warn(this, "Ignored disallowed setting '" + name + "'"); + return false; + } + return true; + } + + private void settingsChanged() { + // NOTE: Merge currently only supports TypeDefDB default settings changes which correspond + // to TypeDefSettingsDefinition established by the base datatype + // and does not consider DataTypeComponent default settings changes or other setting types. + dataMgr.dataTypeChanged(dataType, false); + } + + @Override + public Long getLong(String name) { + SettingDB settingDB = dataMgr.getSetting(dataTypeID, name); + if (settingDB != null) { + return settingDB.getLongValue(); + } + if (defaultSettings != null) { + return defaultSettings.getLong(name); + } + return null; + } + + @Override + public String getString(String name) { + SettingDB settingDB = dataMgr.getSetting(dataTypeID, name); + if (settingDB != null) { + return settingDB.getStringValue(); + } + if (defaultSettings != null) { + return defaultSettings.getString(name); + } + return null; + } + + @Override + public Object getValue(String name) { + SettingDB settingDB = dataMgr.getSetting(dataTypeID, name); + if (settingDB != null) { + return settingDB.getValue(); + } + if (defaultSettings != null) { + return defaultSettings.getValue(name); + } + return null; + } + + @Override + public void setLong(String name, long value) { + if (checkSetting("long", name) && + dataMgr.updateSettingsRecord(dataTypeID, name, null, value)) { + settingsChanged(); + } + } + + @Override + public void setString(String name, String value) { + if (checkSetting("string", name) && + dataMgr.updateSettingsRecord(dataTypeID, name, value, -1)) { + settingsChanged(); + } + } + + @Override + public void setValue(String name, Object value) { + if (value instanceof Long) { + setLong(name, ((Long) value).longValue()); + } + else if (value instanceof String) { + setString(name, (String) value); + } + else { + throw new IllegalArgumentException("Value is not a known settings type: " + name); + } + } + + @Override + public void clearSetting(String name) { + if (checkSetting(null, name) && dataMgr.clearSetting(dataTypeID, name)) { + settingsChanged(); + } + } + + @Override + public void clearAllSettings() { + if (checkSetting(null, null) && dataMgr.clearAllSettings(dataTypeID)) { + settingsChanged(); + } + } + + @Override + public String[] getNames() { + return dataMgr.getSettingsNames(dataTypeID); + } + + @Override + public boolean isEmpty() { + return getNames().length == 0; + } + + public void setDefaultSettings(Settings settings) { + defaultSettings = settings; + } + + @Override + public Settings getDefaultSettings() { + return defaultSettings; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/FunctionDefinitionDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/FunctionDefinitionDB.java index 3db09c4e2b..e93c951d56 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/FunctionDefinitionDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/FunctionDefinitionDB.java @@ -21,6 +21,7 @@ import java.util.*; import db.DBRecord; import db.Field; import ghidra.docking.settings.Settings; +import ghidra.docking.settings.SettingsImpl; import ghidra.program.database.DBObjectCache; import ghidra.program.model.data.*; import ghidra.program.model.listing.Function; @@ -55,6 +56,11 @@ class FunctionDefinitionDB extends DataTypeDB implements FunctionDefinition { return record.getLongValue(FunctionDefinitionDBAdapter.FUNCTION_DEF_CAT_ID_COL); } + @Override + protected Settings doGetDefaultSettings() { + return SettingsImpl.NO_SETTINGS; + } + private void loadParameters() { parameters = new ArrayList<>(); try { @@ -351,7 +357,7 @@ class FunctionDefinitionDB extends DataTypeDB implements FunctionDefinition { return false; } - checkIsValid(); + validate(lock); if (resolving) { // actively resolving children if (dataType.getUniversalID().equals(getUniversalID())) { return true; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/InstanceSettingsDBAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/InstanceSettingsDBAdapter.java deleted file mode 100644 index c704a916e7..0000000000 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/InstanceSettingsDBAdapter.java +++ /dev/null @@ -1,191 +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.program.database.data; - -import ghidra.program.database.map.AddressMap; -import ghidra.program.model.address.Address; -import ghidra.util.exception.CancelledException; -import ghidra.util.exception.VersionException; -import ghidra.util.task.TaskMonitor; - -import java.io.IOException; - -import db.*; - -/** - * Adapter to access the instance settings database tables. - */ -abstract class InstanceSettingsDBAdapter { - - static final String INSTANCE_TABLE_NAME = "Instance Settings"; - - static final Schema INSTANCE_SCHEMA = InstanceSettingsDBAdapterV0.V0_INSTANCE_SCHEMA; - - // Instance Settings Columns - static final int INST_ADDR_COL = InstanceSettingsDBAdapterV0.V0_INST_ADDR_COL; - static final int INST_NAME_COL = InstanceSettingsDBAdapterV0.V0_INST_NAME_COL; - static final int INST_LONG_VALUE_COL = InstanceSettingsDBAdapterV0.V0_INST_LONG_VALUE_COL; - static final int INST_STRING_VALUE_COL = InstanceSettingsDBAdapterV0.V0_INST_STRING_VALUE_COL; - static final int INST_BYTE_VALUE_COL = InstanceSettingsDBAdapterV0.V0_INST_BYTE_VALUE_COL; - - static InstanceSettingsDBAdapter getAdapter(DBHandle handle, int openMode, AddressMap addrMap, - TaskMonitor monitor) throws VersionException, CancelledException, IOException { - - if (openMode == DBConstants.CREATE) { - new InstanceSettingsDBAdapterV0(handle, true); - } - - try { - InstanceSettingsDBAdapter adapter = new InstanceSettingsDBAdapterV0(handle, false); - if (addrMap.isUpgraded()) { - throw new VersionException(true); - } - return adapter; - } - catch (VersionException e) { - if (!e.isUpgradable() || openMode == DBConstants.UPDATE) { - throw e; - } - InstanceSettingsDBAdapter adapter = findReadOnlyAdapter(handle); - if (openMode == DBConstants.UPGRADE) { - adapter = upgrade(handle, adapter, addrMap, monitor); - } - return adapter; - } - } - - private static InstanceSettingsDBAdapter findReadOnlyAdapter(DBHandle dbHandle) - throws VersionException, IOException { - return new InstanceSettingsDBAdapterV0(dbHandle, false); - } - - private static InstanceSettingsDBAdapter upgrade(DBHandle dbHandle, - InstanceSettingsDBAdapter oldAdapter, AddressMap addrMap, TaskMonitor monitor) - throws VersionException, IOException, CancelledException { - - monitor.setMessage("Upgrading Instance Data Settings..."); - monitor.initialize(2 * oldAdapter.getRecordCount()); - int cnt = 0; - - AddressMap oldAddrMap = addrMap.getOldAddressMap(); - - DBHandle tmpHandle = new DBHandle(); - InstanceSettingsDBAdapter tmpAdapter = null; - try { - tmpHandle.startTransaction(); - - tmpAdapter = new InstanceSettingsDBAdapterV0(tmpHandle, true); - RecordIterator iter = oldAdapter.getRecords(); - while (iter.hasNext()) { - if (monitor.isCancelled()) { - throw new CancelledException(); - } - DBRecord rec = iter.next(); - Address addr = oldAddrMap.decodeAddress(rec.getLongValue(INST_ADDR_COL)); - rec.setLongValue(INST_ADDR_COL, addrMap.getKey(addr, true)); - tmpAdapter.updateInstanceRecord(rec); - monitor.setProgress(++cnt); - } - - dbHandle.deleteTable(INSTANCE_TABLE_NAME); - InstanceSettingsDBAdapter newAdapter = new InstanceSettingsDBAdapterV0(dbHandle, true); - iter = tmpAdapter.getRecords(); - while (iter.hasNext()) { - if (monitor.isCancelled()) { - throw new CancelledException(); - } - DBRecord rec = iter.next(); - newAdapter.updateInstanceRecord(rec); - monitor.setProgress(++cnt); - } - return newAdapter; - } - finally { - tmpHandle.close(); - } - } - - /** - * Returns number of settings records - */ - abstract int getRecordCount(); - - /** - * Create an instance settings record. - * @param addr address where setting is applied - * @param name name of the setting - * @param strValue string value; null if setting is not String - * @param longValue long value; -1 if setting is not a long - * @param byteValue byte array value; null if setting is not a byte array - * @return - * @throws IOException if there was a problem accessing the database - */ - abstract DBRecord createInstanceRecord(long addr, String name, String strValue, long longValue, - byte[] byteValue) throws IOException; - - /** - * Get keys for the instance settings applied at the given address. - * @throws IOException if there was a problem accessing the database - */ - abstract Field[] getInstanceKeys(long addr) throws IOException; - - /** - * Remove the instance record. - * @param settingsID key - * @return true if the record was deleted - * @throws IOException if there was a problem accessing the database - */ - abstract boolean removeInstanceRecord(long settingsID) throws IOException; - - /** - * Get the instance settings record. - * @param settingsID key for the record - * @throws IOException if there was a problem accessing the database - */ - abstract DBRecord getInstanceRecord(long settingsID) throws IOException; - - /** - * Update the instance settings record in the table. - * @throws IOException if there was a problem accessing the database - */ - abstract void updateInstanceRecord(DBRecord record) throws IOException; - - /** - * Get an iterator over those records that fall in the given range for - * the address column in the table. - * @param start start address index - * @param end end address index - * @throws IOException if there was a problem accessing the database - */ - abstract RecordIterator getRecords(long start, long end) throws IOException; - - /** - * Returns an iterator over all instance setting records (no specific order) - * @throws IOException - */ - abstract RecordIterator getRecords() throws IOException; - - /** - * Delete all instance settings over a range of addresses. - * @param start - * @param end - * @param monitor - * @throws CancelledException - * @throws IOException - */ - abstract void delete(long start, long end, TaskMonitor monitor) throws CancelledException, - IOException; -} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/InstanceSettingsDBAdapterV0.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/InstanceSettingsDBAdapterV0.java deleted file mode 100644 index 6596d60ed1..0000000000 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/InstanceSettingsDBAdapterV0.java +++ /dev/null @@ -1,129 +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.program.database.data; - -import java.io.IOException; - -import db.*; -import ghidra.util.exception.CancelledException; -import ghidra.util.exception.VersionException; -import ghidra.util.task.TaskMonitor; - -/** - * - */ -class InstanceSettingsDBAdapterV0 extends InstanceSettingsDBAdapter { - - // Instance Settings Columns - static final int V0_INST_ADDR_COL = 0; - static final int V0_INST_NAME_COL = 1; - static final int V0_INST_LONG_VALUE_COL = 2; - static final int V0_INST_STRING_VALUE_COL = 3; - static final int V0_INST_BYTE_VALUE_COL = 4; - - static final Schema V0_INSTANCE_SCHEMA = new Schema(0, "Settings ID", - new Field[] { LongField.INSTANCE, StringField.INSTANCE, LongField.INSTANCE, - StringField.INSTANCE, BinaryField.INSTANCE }, - new String[] { "Address", "Settings Name", "Long Value", "String Value", "Byte Value" }); - - private Table instanceTable; - - /** - * Constructor - * - */ - InstanceSettingsDBAdapterV0(DBHandle handle, boolean create) - throws VersionException, IOException { - - if (create) { - instanceTable = handle.createTable(INSTANCE_TABLE_NAME, V0_INSTANCE_SCHEMA, - new int[] { V0_INST_ADDR_COL }); - } - else { - instanceTable = handle.getTable(INSTANCE_TABLE_NAME); - if (instanceTable == null) { - throw new VersionException("Missing Table: " + INSTANCE_TABLE_NAME); - } - if (instanceTable.getSchema().getVersion() != 0) { - throw new VersionException("Expected version 0 for table " + INSTANCE_TABLE_NAME + - " but got " + instanceTable.getSchema().getVersion()); - } - } - } - - @Override - public DBRecord createInstanceRecord(long addr, String name, String strValue, long longValue, - byte[] byteValue) throws IOException { - - DBRecord record = V0_INSTANCE_SCHEMA.createRecord(instanceTable.getKey()); - record.setLongValue(V0_INST_ADDR_COL, addr); - record.setString(V0_INST_NAME_COL, name); - record.setString(V0_INST_STRING_VALUE_COL, strValue); - record.setLongValue(V0_INST_LONG_VALUE_COL, longValue); - record.setBinaryData(V0_INST_BYTE_VALUE_COL, byteValue); - instanceTable.putRecord(record); - return record; - } - - @Override - public Field[] getInstanceKeys(long addr) throws IOException { - return instanceTable.findRecords(new LongField(addr), V0_INST_ADDR_COL); - } - - @Override - public boolean removeInstanceRecord(long settingsID) throws IOException { - return instanceTable.deleteRecord(settingsID); - } - - @Override - public DBRecord getInstanceRecord(long settingsID) throws IOException { - return instanceTable.getRecord(settingsID); - } - - @Override - public void updateInstanceRecord(DBRecord record) throws IOException { - instanceTable.putRecord(record); - } - - @Override - public RecordIterator getRecords(long start, long end) throws IOException { - - return instanceTable.indexIterator(V0_INST_ADDR_COL, new LongField(start), - new LongField(end), true); - } - - @Override - RecordIterator getRecords() throws IOException { - return instanceTable.iterator(); - } - - @Override - int getRecordCount() { - return instanceTable.getRecordCount(); - } - - @Override - void delete(long start, long end, TaskMonitor monitor) throws CancelledException, IOException { - DBFieldIterator it = instanceTable.indexKeyIterator(V0_INST_ADDR_COL, new LongField(start), - new LongField(end), true); - while (it.hasNext()) { - if (monitor.isCancelled()) { - throw new CancelledException(); - } - instanceTable.deleteRecord(it.next()); - } - } -} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerDB.java index 23d5fe9314..5ecedcfa4c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerDB.java @@ -240,12 +240,7 @@ class PointerDB extends DataTypeDB implements Pointer { lock.acquire(); try { checkIsValid(); - - // TODO: Which address space should pointer refer to ?? - - return PointerDataType.getAddressValue(buf, getLength(), - buf.getAddress().getAddressSpace()); - + return PointerDataType.getAddressValue(buf, getLength(), settings); } catch (IllegalArgumentException exc) { return null; @@ -260,6 +255,11 @@ class PointerDB extends DataTypeDB implements Pointer { return Address.class; } + @Override + public TypeDefSettingsDefinition[] getTypeDefSettingsDefinitions() { + return PointerDataType.dataType.getTypeDefSettingsDefinitions(); + } + @Override public String getRepresentation(MemBuffer buf, Settings settings, int length) { lock.acquire(); @@ -268,7 +268,7 @@ class PointerDB extends DataTypeDB implements Pointer { Address addr = (Address) getValue(buf, settings, length); if (addr == null) { // could not create address, so return "Not a pointer (NaP)" - return "NaP"; + return NaP; } return addr.toString(); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerDBAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerDBAdapter.java index 2d9499cb1d..3d45173f83 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerDBAdapter.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerDBAdapter.java @@ -18,7 +18,6 @@ package ghidra.program.database.data; import java.io.IOException; import db.*; -import ghidra.program.database.map.AddressMap; import ghidra.util.exception.VersionException; import ghidra.util.task.TaskMonitor; @@ -37,8 +36,8 @@ abstract class PointerDBAdapter implements RecordTranslator { static final int PTR_CATEGORY_COL = 1; static final int PTR_LENGTH_COL = 2; - static PointerDBAdapter getAdapter(DBHandle handle, int openMode, TaskMonitor monitor, - AddressMap addrMap) throws VersionException, IOException { + static PointerDBAdapter getAdapter(DBHandle handle, int openMode, TaskMonitor monitor) + throws VersionException, IOException { if (openMode == DBConstants.CREATE) { return new PointerDBAdapterV2(handle, true); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerTypedefInspector.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerTypedefInspector.java new file mode 100644 index 0000000000..c38023c790 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/PointerTypedefInspector.java @@ -0,0 +1,131 @@ +/* ### + * 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.program.database.data; + +import ghidra.program.model.address.AddressFactory; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.data.*; + +/** + * PointerTypeDefInspector provides utilities for inspecting {@link Pointer} - {@link TypeDef}s. + * These special typedefs allow a modified-pointer datatype to be used for special situations where + * a simple pointer will not suffice and special stored pointer interpretation/handling is required. + *
    + * The various {@link Pointer} modifiers on the associated {@link TypeDef} are achieved through the use of various + * {@link TypeDefSettingsDefinition}. The {@link PointerTypedefBuilder} may be used to simplify the creation + * of these pointer-typedefs. + */ +public class PointerTypedefInspector { + + private PointerTypedefInspector() { + // no construct static utility class + } + + /** + * Determine the component-offset for the specified pointerTypeDef based upon + * its default settings. + * @param pointerTypeDef Pointer TypeDef + * @return pointer component offset or 0 if unspecified or not applicable + */ + public static long getPointerComponentOffset(TypeDef pointerTypeDef) { + return pointerTypeDef.isPointer() + ? ComponentOffsetSettingsDefinition.DEF + .getValue(pointerTypeDef.getDefaultSettings()) + : 0; + } + + /** + * Determine the referenced address space for specified pointerTypeDef based upon + * its default settings. + * @param pointerTypeDef Pointer TypeDef + * @param addrFactory target address factory + * @return referenced address space or null if not applicable + * or the space setting is not defined within the addrFactory. + */ + public static AddressSpace getPointerAddressSpace(TypeDef pointerTypeDef, + AddressFactory addrFactory) { + if (!pointerTypeDef.isPointer()) { + return null; + } + String spaceName = + AddressSpaceSettingsDefinition.DEF.getValue(pointerTypeDef.getDefaultSettings()); + if (spaceName == null) { + return null; + } + return addrFactory.getAddressSpace(spaceName); + } + + /** + * Determine if the specified pointerTypeDef has a pointer bit-shift specified. + * @param pointerTypeDef Pointer TypeDef + * @return true if non-zero bit-shift setting exists, else false + */ + public static boolean hasPointerBitShift(TypeDef pointerTypeDef) { + return pointerTypeDef.isPointer() + ? OffsetShiftSettingsDefinition.DEF.hasValue(pointerTypeDef.getDefaultSettings()) + : false; + } + + /** + * Determine the pointer bit-shift for the specified pointerTypeDef based upon + * its default settings. A right-shift is specified by a positive value while + * a left-shift is specified by a negative value. + * If specified, bit-shift will be applied after applying any specified bit-mask. + * @param pointerTypeDef Pointer TypeDef + * @return pointer bit-shift or 0 if unspecified or not applicable + */ + public static long getPointerBitShift(TypeDef pointerTypeDef) { + return pointerTypeDef.isPointer() + ? OffsetShiftSettingsDefinition.DEF.getValue(pointerTypeDef.getDefaultSettings()) + : 0; + } + + /** + * Determine if the specified pointerTypeDef has a pointer bit-mask specified. + * @param pointerTypeDef Pointer TypeDef + * @return true if a bit-mask setting exists, else false + */ + public static boolean hasPointerBitMask(TypeDef pointerTypeDef) { + return pointerTypeDef.isPointer() + ? OffsetMaskSettingsDefinition.DEF.hasValue(pointerTypeDef.getDefaultSettings()) + : false; + } + + /** + * Determine the pointer bit-mask for the specified pointerTypeDef based upon + * its default settings. If specified, bit-mask will be AND-ed with stored + * offset prior to any specified bit-shift. + * @param pointerTypeDef Pointer TypeDef + * @return pointer bit-shift or 0 if unspecified or not applicable + */ + public static long getPointerBitMask(TypeDef pointerTypeDef) { + return pointerTypeDef.isPointer() + ? OffsetMaskSettingsDefinition.DEF.getValue(pointerTypeDef.getDefaultSettings()) + : 0; + } + + /** + * Get the pointer type (see {@link PointerType}). + * @param pointerTypeDef Pointer TypeDef + * @return pointer type or null if not a pointer + */ + public static PointerType getPointerType(TypeDef pointerTypeDef) { + return pointerTypeDef.isPointer() + ? PointerTypeSettingsDefinition.DEF.getType(pointerTypeDef.getDefaultSettings()) + : null; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ProgramBasedDataTypeManagerDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ProgramBasedDataTypeManagerDB.java new file mode 100644 index 0000000000..8f74f0547e --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ProgramBasedDataTypeManagerDB.java @@ -0,0 +1,383 @@ +/* ### + * 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.program.database.data; + +import java.io.IOException; +import java.util.List; + +import db.*; +import db.util.ErrorHandler; +import ghidra.program.database.map.AddressMap; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.KeyRange; +import ghidra.program.model.data.ProgramBasedDataTypeManager; +import ghidra.util.Lock; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.VersionException; +import ghidra.util.task.TaskMonitor; + +/** + * DB-based Program datatype manager implementation + * which has the concept of an address-based listing and corresponding + * datatype instance settings. + */ +public abstract class ProgramBasedDataTypeManagerDB extends DataTypeManagerDB + implements ProgramBasedDataTypeManager { + + private static final String INSTANCE_SETTINGS_TABLE_NAME = "Instance Settings"; + + private SettingsDBAdapter instanceSettingsAdapter; + private SettingsCache
    instanceSettingsCache = new SettingsCache<>(200); + + /** + * Constructor + * @param handle open database handle + * @param addrMap the address map (instance settings not supported if null) + * @param openMode the program open mode (see {@link DBConstants}) + * @param errHandler the database io error handler + * @param lock the program synchronization lock + * @param monitor the progress monitor + * @throws CancelledException if the user cancels an upgrade + * @throws VersionException if the database does not match the expected version. + * @throws IOException if a database IO error occurs. + */ + public ProgramBasedDataTypeManagerDB(DBHandle handle, AddressMap addrMap, int openMode, + ErrorHandler errHandler, Lock lock, TaskMonitor monitor) + throws CancelledException, VersionException, IOException { + super(handle, addrMap, openMode, errHandler, lock, monitor); + } + + protected void initializeOtherAdapters(int openMode, TaskMonitor monitor) + throws CancelledException, IOException, VersionException { + if (addrMap != null) { + instanceSettingsAdapter = + SettingsDBAdapter.getAdapter(INSTANCE_SETTINGS_TABLE_NAME, dbHandle, openMode, + addrMap, monitor); + } + } + + @Override + public void invalidateCache() { + lock.acquire(); + try { + super.invalidateCache(); + if (instanceSettingsAdapter != null) { + instanceSettingsAdapter.invalidateNameCache(); + instanceSettingsCache.clear(); + } + } + finally { + lock.release(); + } + } + + /** + * Provides notification when a data instance setting has changed at a specific address. + * @param address data address + */ + abstract protected void dataSettingChanged(Address address); + + @Override + public boolean setLongSettingsValue(Address dataAddr, String name, long value) { + return updateInstanceSettings(dataAddr, name, null, value); + } + + @Override + public boolean setStringSettingsValue(Address dataAddr, String name, String value) { + return updateInstanceSettings(dataAddr, name, value, -1); + } + + @Override + public boolean setSettings(Address dataAddr, String name, Object value) { + if (value instanceof String) { + return updateInstanceSettings(dataAddr, name, (String) value, -1); + } + else if (isAllowedNumberType(value)) { + return updateInstanceSettings(dataAddr, name, null, ((Number) value).longValue()); + } + throw new IllegalArgumentException( + "Unsupportd Settings Value: " + (value == null ? "null" : value.getClass().getName())); + } + + private boolean isAllowedNumberType(Object value) { + if (value instanceof Long) { + return true; + } + if (value instanceof Integer) { + return true; + } + if (value instanceof Short) { + return true; + } + if (value instanceof Byte) { + return true; + } + return false; + } + + @Override + public Long getLongSettingsValue(Address dataAddr, String name) { + SettingDB settings = getSettingDB(dataAddr, name); + if (settings != null) { + return settings.getLongValue(); + } + return null; + } + + @Override + public String getStringSettingsValue(Address dataAddr, String name) { + SettingDB settings = getSettingDB(dataAddr, name); + if (settings != null) { + return settings.getStringValue(); + } + return null; + } + + @Override + public Object getSettings(Address dataAddr, String name) { + Object obj = getStringSettingsValue(dataAddr, name); + if (obj != null) { + return obj; + } + return getLongSettingsValue(dataAddr, name); + } + + @Override + public boolean clearSetting(Address dataAddr, String name) { + if (instanceSettingsAdapter == null) { + throw new UnsupportedOperationException(); + } + lock.acquire(); + try { + instanceSettingsCache.remove(dataAddr, name); + long addr = addrMap.getKey(dataAddr, false); + if (instanceSettingsAdapter.removeSettingsRecord(addr, name)) { + dataSettingChanged(dataAddr); + return true; + } + } + catch (IOException e) { + errHandler.dbError(e); + + } + finally { + lock.release(); + } + return false; + } + + @Override + public void clearAllSettings(Address dataAddr) { + if (instanceSettingsAdapter == null) { + throw new UnsupportedOperationException(); + } + lock.acquire(); + try { + instanceSettingsCache.clear(); + boolean changed = false; + Field[] keys = instanceSettingsAdapter.getSettingsKeys(addrMap.getKey(dataAddr, false)); + for (Field key : keys) { + instanceSettingsAdapter.removeSettingsRecord(key.getLongValue()); + changed = true; + } + if (changed) { + dataSettingChanged(dataAddr); + } + } + catch (IOException e) { + errHandler.dbError(e); + + } + finally { + lock.release(); + } + } + + @Override + public void moveAddressRange(Address fromAddr, Address toAddr, long length, TaskMonitor monitor) + throws CancelledException { + if (instanceSettingsAdapter == null) { + throw new UnsupportedOperationException(); + } + + DBHandle scratchPad = null; + lock.acquire(); + try { + instanceSettingsCache.clear(); + scratchPad = dbHandle.getScratchPad(); + Table tmpTable = scratchPad.createTable(INSTANCE_SETTINGS_TABLE_NAME, + SettingsDBAdapterV1.V1_SETTINGS_SCHEMA); + + List keyRanges = + addrMap.getKeyRanges(fromAddr, fromAddr.add(length - 1), false); + for (KeyRange range : keyRanges) { + RecordIterator iter = + instanceSettingsAdapter.getRecords(range.minKey, range.maxKey); + while (iter.hasNext()) { + monitor.checkCanceled(); + DBRecord rec = iter.next(); + tmpTable.putRecord(rec); + iter.delete(); + } + } + + RecordIterator iter = tmpTable.iterator(); + while (iter.hasNext()) { + monitor.checkCanceled(); + DBRecord rec = iter.next(); + // update address key (i.e., settings association ID) and re-introduce into table + Address addr = addrMap + .decodeAddress( + rec.getLongValue(SettingsDBAdapter.SETTINGS_ASSOCIATION_ID_COL)); + long offset = addr.subtract(fromAddr); + addr = toAddr.add(offset); + rec.setLongValue(SettingsDBAdapter.SETTINGS_ASSOCIATION_ID_COL, + addrMap.getKey(addr, true)); + instanceSettingsAdapter.updateSettingsRecord(rec); + } + + } + catch (IOException e) { + errHandler.dbError(e); + } + finally { + if (scratchPad != null) { + try { + scratchPad.deleteTable(INSTANCE_SETTINGS_TABLE_NAME); + } + catch (IOException e) { + // ignore + } + } + lock.release(); + } + } + + @Override + public String[] getInstanceSettingsNames(Address dataAddr) { + if (instanceSettingsAdapter == null) { + throw new UnsupportedOperationException(); + } + lock.acquire(); + try { + return instanceSettingsAdapter.getSettingsNames(addrMap.getKey(dataAddr, false)); + } + catch (IOException e) { + errHandler.dbError(e); + } + finally { + lock.release(); + } + return new String[0]; + } + + @Override + public boolean isEmptySetting(Address dataAddr) { + if (instanceSettingsAdapter == null) { + throw new UnsupportedOperationException(); + } + try { + return instanceSettingsAdapter + .getSettingsKeys(addrMap.getKey(dataAddr, false)).length == 0; + } + catch (IOException e) { + errHandler.dbError(e); + } + return true; + } + + private boolean updateInstanceSettings(Address dataAddr, String name, String strValue, + long longValue) { + + boolean wasChanged = false; + + lock.acquire(); + try { + if (instanceSettingsAdapter == null) { + throw new UnsupportedOperationException(); + } + long addrKey = addrMap.getKey(dataAddr, true); + DBRecord rec = + instanceSettingsAdapter.updateSettingsRecord(addrKey, name, strValue, longValue); + if (rec != null) { + SettingDB setting = new SettingDB(rec, instanceSettingsAdapter.getSettingName(rec)); + instanceSettingsCache.put(dataAddr, name, setting); + dataSettingChanged(dataAddr); + return true; + } + return false; + } + catch (IOException e) { + errHandler.dbError(e); + } + finally { + lock.release(); + } + + return wasChanged; + } + + private SettingDB getSettingDB(Address dataAddr, String name) { + lock.acquire(); + try { + if (instanceSettingsAdapter == null) { + throw new UnsupportedOperationException(); + } + SettingDB settings = instanceSettingsCache.get(dataAddr, name); + if (settings != null) { + return settings; + } + long addr = addrMap.getKey(dataAddr, false); + DBRecord rec = instanceSettingsAdapter.getSettingsRecord(addr, name); + if (rec != null) { + settings = new SettingDB(rec, instanceSettingsAdapter.getSettingName(rec)); + instanceSettingsCache.put(dataAddr, name, settings); + return settings; + } + } + catch (IOException e) { + errHandler.dbError(e); + } + finally { + lock.release(); + } + return null; + } + + @Override + public void deleteAddressRange(Address startAddr, Address endAddr, TaskMonitor monitor) + throws CancelledException { + if (instanceSettingsAdapter == null) { + throw new UnsupportedOperationException(); + } + lock.acquire(); + try { + List addrKeyRanges = addrMap.getKeyRanges(startAddr, endAddr, false); + int cnt = addrKeyRanges.size(); + for (int i = 0; i < cnt; i++) { + KeyRange kr = (KeyRange) addrKeyRanges.get(i); + instanceSettingsAdapter.delete(kr.minKey, kr.maxKey, monitor); + } + } + catch (IOException e) { + dbError(e); + } + finally { + instanceSettingsCache.clear(); + lock.release(); + } + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ProgramDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ProgramDataTypeManager.java index e1332860a0..fbfddf7562 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ProgramDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/ProgramDataTypeManager.java @@ -26,9 +26,8 @@ import ghidra.framework.model.DomainFile; import ghidra.framework.options.Options; import ghidra.program.database.ManagerDB; import ghidra.program.database.ProgramDB; -import ghidra.program.database.function.FunctionManagerDB; import ghidra.program.database.map.AddressMap; -import ghidra.program.database.symbol.SymbolManager; +import ghidra.program.model.address.Address; import ghidra.program.model.data.*; import ghidra.program.model.listing.Program; import ghidra.program.util.ChangeManager; @@ -40,8 +39,8 @@ import ghidra.util.task.TaskMonitor; /** * Class for managing data types in a program */ -public class ProgramDataTypeManager extends DataTypeManagerDB - implements ManagerDB, ProgramBasedDataTypeManager { +public class ProgramDataTypeManager extends ProgramBasedDataTypeManagerDB + implements ManagerDB { private static final String OLD_DT_ARCHIVE_FILENAMES = "DataTypeArchiveFilenames"; // eliminated with Ghidra 4.3 @@ -52,13 +51,13 @@ public class ProgramDataTypeManager extends DataTypeManagerDB * Constructor * @param handle open database handle * @param addrMap the address map - * @param openMode the program open mode + * @param openMode the program open mode (see {@link DBConstants}) * @param errHandler the database io error handler * @param lock the program synchronization lock * @param monitor the progress monitor * @throws CancelledException if the user cancels an upgrade * @throws VersionException if the database does not match the expected version. - * @throws IOException if a database io error occurs. + * @throws IOException if a database IO error occurs. */ public ProgramDataTypeManager(DBHandle handle, AddressMap addrMap, int openMode, ErrorHandler errHandler, Lock lock, TaskMonitor monitor) @@ -67,6 +66,17 @@ public class ProgramDataTypeManager extends DataTypeManagerDB upgrade = (openMode == DBConstants.UPGRADE); } + @Override + protected void dataSettingChanged(Address dataAddr) { + program.setChanged(ChangeManager.DOCR_DATA_TYPE_SETTING_CHANGED, dataAddr, + dataAddr, null, null); + } + + @Override + public boolean allowsDefaultBuiltInSettings() { + return true; + } + @Override public void setProgram(ProgramDB p) { this.program = p; @@ -103,11 +113,6 @@ public class ProgramDataTypeManager extends DataTypeManagerDB return program.getName(); } - @Override - public Pointer getPointer(DataType dt) { - return PointerDataType.getPointer(dt, this); - } - @Override public void setName(String name) throws InvalidNameException { if (name == null || name.length() == 0) { @@ -213,14 +218,16 @@ public class ProgramDataTypeManager extends DataTypeManagerDB return; } program.getCodeManager().replaceDataTypes(oldDataTypeID, newDataTypeID); - ((SymbolManager) program.getSymbolTable()).replaceDataTypes(oldDataTypeID, newDataTypeID); - ((FunctionManagerDB) program.getFunctionManager()).replaceDataTypes(oldDataTypeID, - newDataTypeID); + program.getSymbolTable().replaceDataTypes(oldDataTypeID, newDataTypeID); + program.getFunctionManager().replaceDataTypes(oldDataTypeID, newDataTypeID); } @Override protected void deleteDataTypeIDs(LinkedList deletedIds, TaskMonitor monitor) throws CancelledException { + // TODO: SymbolManager/FunctionManager do not appear to handle datatype removal update. + // Suspect it handles indirectly through detection of deleted datatype. Old deleted ID + // use could be an issue. long[] ids = new long[deletedIds.size()]; Iterator it = deletedIds.iterator(); int i = 0; @@ -292,4 +299,5 @@ public class ProgramDataTypeManager extends DataTypeManagerDB } return dataOrganization; } + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingDB.java similarity index 64% rename from Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDB.java rename to Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingDB.java index 1e6912b039..217ef17c75 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingDB.java @@ -18,32 +18,31 @@ package ghidra.program.database.data; import db.DBRecord; /** - * DatabaseObject for a Default settings record. - * - * + * Setting DBRecord wrapper for cache use */ -class SettingsDB { +class SettingDB { + + private String name; private DBRecord record; /** - * Constructor - * @param cache - * @param record + * Construction setting object + * @param record setting record + * @param name setting name */ - SettingsDB(DBRecord record) { + SettingDB(DBRecord record, String name) { this.record = record; + this.name = name; } String getName() { - return record.getString(SettingsDBAdapter.SETTINGS_NAME_COL); + return name; } Long getLongValue() { - Long lvalue = null; - if (getStringValue() == null && getByteValue() == null) { - long l = record.getLongValue(SettingsDBAdapter.SETTINGS_LONG_VALUE_COL); - lvalue = new Long(l); + if (getStringValue() == null) { + lvalue = record.getLongValue(SettingsDBAdapter.SETTINGS_LONG_VALUE_COL); } return lvalue; } @@ -52,19 +51,19 @@ class SettingsDB { return record.getString(SettingsDBAdapter.SETTINGS_STRING_VALUE_COL); } - byte[] getByteValue() { - return record.getBinaryData(SettingsDBAdapter.SETTINGS_BYTE_VALUE_COL); - } - Object getValue() { Object obj = getStringValue(); if (obj != null) { return obj; } - obj = getByteValue(); - if (obj != null) { - return obj; - } - return getLongValue(); + return record.getLongValue(SettingsDBAdapter.SETTINGS_LONG_VALUE_COL); + } + + long getKey() { + return record.getKey(); + } + + DBRecord getRecord() { + return record; } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsCache.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsCache.java index 05a803a852..256c86bfd3 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsCache.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsCache.java @@ -16,59 +16,82 @@ package ghidra.program.database.data; import java.util.LinkedHashMap; +import java.util.Objects; -import ghidra.program.model.address.Address; import ghidra.util.datastruct.FixedSizeHashMap; -class SettingsCache { - private static final int CACHE_SIZE = 200; +class SettingsCache { - class AddressNamePair { - Address address; + private static class IdNamePair { + Object id; // object which corresponds to association ID (e.g., Address, DataType-ID) String name; - AddressNamePair(Address address, String name) { - this.address = address; + IdNamePair(Object id, String name) { + this.id = id; this.name = name; } @Override public boolean equals(Object obj) { - if (!(obj instanceof AddressNamePair)) { + if (!(obj instanceof IdNamePair)) { return false; } - AddressNamePair other = (AddressNamePair) obj; - return other.address.equals(address) && other.name.equals(name); + IdNamePair other = (IdNamePair) obj; + return other.id.equals(id) && other.name.equals(name); } @Override public int hashCode() { - return address.hashCode() + name.hashCode(); + return Objects.hash(id, name); } } - private LinkedHashMap map; + private LinkedHashMap map; - SettingsCache() { - map = new FixedSizeHashMap<>(CACHE_SIZE, CACHE_SIZE); + /** + * Construct settings cache of a specified size + * @param size cache size (maximum number of entries held) + */ + SettingsCache(int size) { + map = new FixedSizeHashMap<>(size, size); } - public void remove(Address address, String name) { - AddressNamePair key = new AddressNamePair(address, name); + /** + * Remove specific setting record from cache + * @param id association ID object (e.g., Address, DataType ID) + * @param name name of setting + */ + public void remove(K id, String name) { + IdNamePair key = new IdNamePair(id, name); map.remove(key); } + /** + * Clear all cached entries + */ void clear() { map.clear(); } - InstanceSettingsDB getInstanceSettings(Address address, String name) { - AddressNamePair key = new AddressNamePair(address, name); + /** + * Get a cached setting record + * @param id association ID object (e.g., Address, DataType ID) + * @param name name of setting + * @return cached setting or null if not found + */ + SettingDB get(K id, String name) { + IdNamePair key = new IdNamePair(id, name); return map.get(key); } - void put(Address address, String name, InstanceSettingsDB settings) { - AddressNamePair key = new AddressNamePair(address, name); - map.put(key, settings); + /** + * Add setting record to cache + * @param id association ID object (e.g., Address, DataType ID) + * @param name name of setting + * @param setting object + */ + void put(K id, String name, SettingDB setting) { + IdNamePair key = new IdNamePair(id, name); + map.put(key, setting); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDBAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDBAdapter.java index 52bc02cca7..3b39b6719e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDBAdapter.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDBAdapter.java @@ -18,32 +18,148 @@ package ghidra.program.database.data; import java.io.IOException; import db.*; +import ghidra.program.database.map.AddressMap; +import ghidra.program.model.address.Address; +import ghidra.util.exception.CancelledException; import ghidra.util.exception.VersionException; import ghidra.util.task.TaskMonitor; /** - * Adapter to access the default settings and instance settings database tables. + * Adapter to access settings database tables. * * */ abstract class SettingsDBAdapter { - static final String SETTINGS_TABLE_NAME = "Default Settings"; - - static final Schema SETTINGS_SCHEMA = SettingsDBAdapterV0.V0_SETTINGS_SCHEMA; + static final Schema SETTINGS_SCHEMA = SettingsDBAdapterV1.V1_SETTINGS_SCHEMA; // Default Settings Columns - static final int SETTINGS_DT_ID_COL = SettingsDBAdapterV0.V0_SETTINGS_DT_ID_COL; - static final int SETTINGS_NAME_COL = SettingsDBAdapterV0.V0_SETTINGS_NAME_COL; - static final int SETTINGS_LONG_VALUE_COL = SettingsDBAdapterV0.V0_SETTINGS_LONG_VALUE_COL; - static final int SETTINGS_STRING_VALUE_COL = SettingsDBAdapterV0.V0_SETTINGS_STRING_VALUE_COL; - static final int SETTINGS_BYTE_VALUE_COL = SettingsDBAdapterV0.V0_SETTINGS_BYTE_VALUE_COL; + static final int SETTINGS_ASSOCIATION_ID_COL = + SettingsDBAdapterV1.V1_SETTINGS_ASSOCIATION_ID_COL; // e.g., Address-key or Datatype-ID + static final int SETTINGS_NAME_INDEX_COL = SettingsDBAdapterV1.V1_SETTINGS_NAME_INDEX_COL; // short + static final int SETTINGS_LONG_VALUE_COL = SettingsDBAdapterV1.V1_SETTINGS_LONG_VALUE_COL; + static final int SETTINGS_STRING_VALUE_COL = SettingsDBAdapterV1.V1_SETTINGS_STRING_VALUE_COL; - static SettingsDBAdapter getAdapter(DBHandle handle, int openMode, TaskMonitor monitor) - throws VersionException, IOException { - return new SettingsDBAdapterV0(handle, openMode == DBConstants.CREATE); + /** + * Get a settings adapter. + * NOTE: Support for read-only mode for older versions must be retained. + * @param tableName settings DB table name + * @param handle database handle + * @param openMode database open mode + * @param addrMap address map (should only be specified when association IDs + * correspond to address key, otherwise should be null). + * @param monitor task monitor + * @return settings adapter instance + * @throws VersionException if schema version does not match current version + * @throws IOException if there was a problem accessing the database + * @throws CancelledException if task cancelled + */ + static SettingsDBAdapter getAdapter(String tableName, DBHandle handle, int openMode, + AddressMap addrMap, TaskMonitor monitor) + throws VersionException, IOException, CancelledException { + + if (openMode == DBConstants.CREATE) { + return new SettingsDBAdapterV1(tableName, handle, true); + } + + if (openMode == DBConstants.READ_ONLY) { + return findReadOnlyAdapter(tableName, handle); + } + + try { + SettingsDBAdapter adapter = new SettingsDBAdapterV1(tableName, handle, false); + if (addrMap != null && addrMap.isUpgraded()) { + throw new VersionException(true); + } + return adapter; + } + catch (VersionException e) { + if (!e.isUpgradable() || openMode == DBConstants.UPDATE) { + throw e; + } + SettingsDBAdapter adapter = findReadOnlyAdapter(tableName, handle); + if (openMode == DBConstants.UPGRADE) { + adapter = upgrade(handle, adapter, addrMap, monitor); + } + return adapter; + } } + private static SettingsDBAdapter findReadOnlyAdapter(String tableName, DBHandle dbHandle) + throws VersionException, IOException { + try { + return new SettingsDBAdapterV1(tableName, dbHandle, false); + } + catch (VersionException e) { + return new SettingsDBAdapterV0(tableName, dbHandle); + } + } + + private static SettingsDBAdapter upgrade(DBHandle dbHandle, SettingsDBAdapter oldAdapter, + AddressMap addrMap, TaskMonitor monitor) + throws VersionException, IOException, CancelledException { + + String tableName = oldAdapter.getTableName(); + monitor.setMessage("Upgrading " + tableName + "..."); + monitor.initialize(2 * oldAdapter.getRecordCount()); + int cnt = 0; + + AddressMap oldAddrMap = addrMap != null ? addrMap.getOldAddressMap() : null; + + DBHandle tmpHandle = new DBHandle(); + SettingsDBAdapter tmpAdapter = null; + try { + tmpHandle.startTransaction(); + + tmpAdapter = new SettingsDBAdapterV1(tableName, tmpHandle, true); + RecordIterator iter = oldAdapter.getRecords(); + while (iter.hasNext()) { + if (monitor.isCancelled()) { + throw new CancelledException(); + } + DBRecord rec = iter.next(); + if (oldAddrMap != addrMap) { + // updated address-based association ID if needed + Address addr = + oldAddrMap.decodeAddress(rec.getLongValue(SETTINGS_ASSOCIATION_ID_COL)); + rec.setLongValue(SETTINGS_ASSOCIATION_ID_COL, addrMap.getKey(addr, true)); + } + tmpAdapter.createSettingsRecord(rec.getLongValue(SETTINGS_ASSOCIATION_ID_COL), + oldAdapter.getSettingName(rec), + rec.getString(SETTINGS_STRING_VALUE_COL), + rec.getLongValue(SETTINGS_LONG_VALUE_COL)); + monitor.setProgress(++cnt); + } + + // NOTE: If a V2 adapter is ever added multiple tables may need to be removed. + dbHandle.deleteTable(tableName); + + SettingsDBAdapter newAdapter = new SettingsDBAdapterV1(tableName, dbHandle, true); + iter = tmpAdapter.getRecords(); + while (iter.hasNext()) { + if (monitor.isCancelled()) { + throw new CancelledException(); + } + DBRecord rec = iter.next(); + newAdapter.createSettingsRecord(rec.getLongValue(SETTINGS_ASSOCIATION_ID_COL), + tmpAdapter.getSettingName(rec), + rec.getString(SETTINGS_STRING_VALUE_COL), + rec.getLongValue(SETTINGS_LONG_VALUE_COL)); + monitor.setProgress(++cnt); + } + return newAdapter; + } + finally { + tmpHandle.close(); + } + } + + /** + * Get DB table name + * @return table name + */ + abstract String getTableName(); + /** * Returns number of settings records * @return total settings record count @@ -51,48 +167,139 @@ abstract class SettingsDBAdapter { abstract int getRecordCount(); /** - * Create a default settings record. - * @param dataTypeID data type ID associated with the setting + * Get iterator over all settings records + * @return record iterator + * @throws IOException if there was a problem accessing the database + */ + abstract RecordIterator getRecords() throws IOException; + + /** + * Get an iterator over those records that fall in the given range for + * the association ID column in the table. + * @param minAssociationId minimum association ID for range + * @param maxAssociationId maximum association ID for range + * @return record iterator + * @throws IOException if there was a problem accessing the database + */ + abstract RecordIterator getRecords(long minAssociationId, long maxAssociationId) + throws IOException; + + /** + * Delete all settings records over the specified range of association IDs + * @param minAssociationId minimum association ID for range + * @param maxAssociationId maximum association ID for range + * @param monitor task monitor + * @throws CancelledException if task was cancelled + * @throws IOException if there was a problem accessing the database + */ + abstract void delete(long minAssociationId, long maxAssociationId, TaskMonitor monitor) + throws CancelledException, IOException; + + /** + * Create a settings record. + * @param associationId ID associated with the use of a setting (e.g., address-key, datatype-ID) * @param name name of the setting * @param strValue string value; null if setting is not String * @param longValue long value; -1 if setting is not a long - * @param byteValue byte array value; null if setting is not a byte array * @return new record * @throws IOException if there was a problem accessing the database */ - abstract DBRecord createSettingsRecord(long dataTypeID, String name, String strValue, - long longValue, byte[] byteValue) throws IOException; + abstract DBRecord createSettingsRecord(long associationId, String name, String strValue, + long longValue) throws IOException; /** - * Get settings record keys for the default settings corresponding to the - * specified data type ID. - * @param dataTypeID datatype ID + * Get settings record keys for all settings corresponding to the + * specified associationId. + * @param associationId ID associated with the use of a setting (e.g., address-key, datatype-ID) * @return settings record keys returned as LongFields within Field array * @throws IOException if there was a problem accessing the database */ - abstract Field[] getSettingsKeys(long dataTypeID) throws IOException; + abstract Field[] getSettingsKeys(long associationId) throws IOException; /** - * Remove the default settings record. - * @param settingsID key for the record + * Remove all settings records for specified associationId. + * @param associationId ID associated with the use of a setting (e.g., address-key, datatype-ID) + * @throws IOException if there was a problem accessing the database + */ + abstract void removeAllSettingsRecords(long associationId) throws IOException; + + /** + * Remove the specified settings record. + * @param settingsId key for the record * @return true if the record was deleted * @throws IOException if there was a problem accessing the database */ - abstract boolean removeSettingsRecord(long settingsID) throws IOException; + abstract boolean removeSettingsRecord(long settingsId) throws IOException; /** - * Get the default settings record. - * @param settingsID key for the record + * Remove the specified settings record if found + * @param associationId association ID (e.g., address key, datatype ID) + * @param name setting name + * @return true if record found and was removed, else false + * @throws IOException if there was a problem accessing the database + */ + abstract boolean removeSettingsRecord(long associationId, String name) throws IOException; + + /** + * Get the specified settings record. + * @param settingsId key for the record * @return record corresponding to settingsID or null * @throws IOException if there was a problem accessing the database */ - abstract DBRecord getSettingsRecord(long settingsID) throws IOException; + abstract DBRecord getSettingsRecord(long settingsId) throws IOException; /** - * Update the default settings record in the table. - * @param record the new record + * Get the settings record which corresponds to a specific associatedId and setting name. + * @param associationId association ID (e.g., address key, datatype ID) + * @param name setting name + * @return record corresponding to settingsID or null * @throws IOException if there was a problem accessing the database */ + abstract DBRecord getSettingsRecord(long associationId, String name) throws IOException; + + /** + * Update the settings record in the table + * IMPORTANT: This method must not be used during upgrades since it bypasses allocation + * of settings name index values. + * @param record the new record + * @throws IOException if there was a problem accessing the database or an invalid + * name index value was used. + */ abstract void updateSettingsRecord(DBRecord record) throws IOException; + /** + * Update the setting record corresponding to the specified setting data. + * Search for existing record will be performed. + * @param associationId association ID (e.g., address key, datatype ID) + * @param name setting name + * @param strValue setting string value or null + * @param longValue setting long value + * @return updated record if setting was updated, else null + * @throws IOException if there was a problem accessing the database + */ + abstract DBRecord updateSettingsRecord(long associationId, String name, String strValue, + long longValue) throws IOException; + + /** + * Get an array of names for settings records which correspond to the specified + * associationId. + * @param associationId association ID (e.g., address key, datatype ID) + * @return array of settings names + * @throws IOException if there was a problem accessing the database + */ + abstract String[] getSettingsNames(long associationId) throws IOException; + + /** + * Get the setting name which corresponds to the specified record. + * @param record normalized settings record (name column is an integer index value) + * @return setting name + * @throws IOException if there was a problem accessing the database + */ + abstract String getSettingName(DBRecord record) throws IOException; + + /** + * Invalidate name cache + */ + abstract void invalidateNameCache(); + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDBAdapterV0.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDBAdapterV0.java index 8ac528c3fc..3387e1868f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDBAdapterV0.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDBAdapterV0.java @@ -16,79 +16,146 @@ package ghidra.program.database.data; import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; import db.*; +import ghidra.util.ReadOnlyException; +import ghidra.util.exception.CancelledException; import ghidra.util.exception.VersionException; +import ghidra.util.task.TaskMonitor; /** * Version 0 implementation for the accessing the data type settings database table. + * This version stored settings name as a string within each record. */ class SettingsDBAdapterV0 extends SettingsDBAdapter { // Default Settings Columns - static final int V0_SETTINGS_DT_ID_COL = 0; - static final int V0_SETTINGS_NAME_COL = 1; + static final int V0_SETTINGS_ASSOCIATION_ID_COL = 0; // e.g., Address-key, Datatype-ID + static final int V0_SETTINGS_NAME_COL = 1; // string - must translate to short index with name map static final int V0_SETTINGS_LONG_VALUE_COL = 2; static final int V0_SETTINGS_STRING_VALUE_COL = 3; - static final int V0_SETTINGS_BYTE_VALUE_COL = 4; + // static final int V0_SETTINGS_BYTE_VALUE_COL = 4; // discarded during V1 upgrade + + private static final int V0_SCHEMA_VERSION = 0; + +// Keep for reference +// static final Schema V0_SETTINGS_SCHEMA = new Schema(0, "SettingsID", +// new Field[] { LongField.INSTANCE, StringField.INSTANCE, LongField.INSTANCE, +// StringField.INSTANCE, BinaryField.INSTANCE }, +// new String[] { "AssociationID", "Settings Name", "Long Value", "String Value", +// "Byte Value" }); - static final Schema V0_SETTINGS_SCHEMA = new Schema(0, "DT Settings ID", - new Field[] { LongField.INSTANCE, StringField.INSTANCE, LongField.INSTANCE, - StringField.INSTANCE, BinaryField.INSTANCE }, - new String[] { "Data Type ID", "Settings Name", "Long Value", "String Value", - "Byte Value" }); private Table settingsTable; - SettingsDBAdapterV0(DBHandle handle, boolean create) throws VersionException, IOException { + private HashMap nameIndexMap = new HashMap<>(); + private HashMap nameStringMap = new HashMap<>(); - if (create) { - settingsTable = handle.createTable(SETTINGS_TABLE_NAME, V0_SETTINGS_SCHEMA, - new int[] { V0_SETTINGS_DT_ID_COL }); + SettingsDBAdapterV0(String tableName, DBHandle handle) + throws VersionException { + + settingsTable = handle.getTable(tableName); + if (settingsTable == null) { + throw new VersionException("Missing Table: " + tableName); } - else { - settingsTable = handle.getTable(SETTINGS_TABLE_NAME); - if (settingsTable == null) { - throw new VersionException("Missing Table: " + SETTINGS_TABLE_NAME); - } - if (settingsTable.getSchema().getVersion() != 0) { - throw new VersionException("Expected version 0 for table " + SETTINGS_TABLE_NAME + - " but got " + settingsTable.getSchema().getVersion()); - } + int ver = settingsTable.getSchema().getVersion(); + if (ver != V0_SCHEMA_VERSION) { + throw new VersionException(false); } } @Override - public DBRecord createSettingsRecord(long dataTypeID, String name, String strValue, - long longValue, byte[] byteValue) throws IOException { - - DBRecord record = V0_SETTINGS_SCHEMA.createRecord(settingsTable.getKey()); - record.setLongValue(V0_SETTINGS_DT_ID_COL, dataTypeID); - record.setString(V0_SETTINGS_NAME_COL, name); - record.setString(V0_SETTINGS_STRING_VALUE_COL, strValue); - record.setLongValue(V0_SETTINGS_LONG_VALUE_COL, longValue); - record.setBinaryData(V0_SETTINGS_BYTE_VALUE_COL, byteValue); - settingsTable.putRecord(record); - return record; + String getTableName() { + return settingsTable.getName(); } @Override - public Field[] getSettingsKeys(long dataTypeID) throws IOException { - return settingsTable.findRecords(new LongField(dataTypeID), V0_SETTINGS_DT_ID_COL); + public DBRecord createSettingsRecord(long associationId, String name, String strValue, + long longValue) throws IOException { + throw new ReadOnlyException(); + } + + @Override + public Field[] getSettingsKeys(long associationId) throws IOException { + return settingsTable.findRecords(new LongField(associationId), + V0_SETTINGS_ASSOCIATION_ID_COL); + } + + @Override + void removeAllSettingsRecords(long associationId) throws IOException { + for (Field key : getSettingsKeys(associationId)) { + removeSettingsRecord(key.getLongValue()); + } } @Override public boolean removeSettingsRecord(long settingsID) throws IOException { - return settingsTable.deleteRecord(settingsID); + throw new ReadOnlyException(); + } + + @Override + boolean removeSettingsRecord(long associationId, String name) throws IOException { + throw new ReadOnlyException(); + } + + @Override + String[] getSettingsNames(long associationId) throws IOException { + ArrayList list = new ArrayList<>(); + for (Field key : getSettingsKeys(associationId)) { + DBRecord rec = settingsTable.getRecord(key); + list.add(rec.getString(V0_SETTINGS_NAME_COL)); + } + String[] names = new String[list.size()]; + return list.toArray(names); + } + + @Override + protected String getSettingName(DBRecord normalizedRecord) { + short nameIndex = normalizedRecord.getShortValue(SettingsDBAdapter.SETTINGS_NAME_INDEX_COL); + return nameIndexMap.get(nameIndex); + } + + @Override + void invalidateNameCache() { + // ignore - name map values can be retained + } + + private short assignNameIndexValue(String name) { + Short index = nameStringMap.get(name); + if (index == null) { + index = (short) nameStringMap.size(); + nameStringMap.put(name, index); + nameIndexMap.put(index, name); + } + return index; } @Override public DBRecord getSettingsRecord(long settingsID) throws IOException { - return settingsTable.getRecord(settingsID); + return translateV0Record(settingsTable.getRecord(settingsID)); + } + + @Override + DBRecord getSettingsRecord(long associationId, String name) throws IOException { + for (Field key : getSettingsKeys(associationId)) { + DBRecord rec = settingsTable.getRecord(key); + if (rec.getString(V0_SETTINGS_NAME_COL).equals(name)) { + return translateV0Record(rec); + } + } + return null; } @Override public void updateSettingsRecord(DBRecord record) throws IOException { - settingsTable.putRecord(record); + throw new ReadOnlyException(); + } + + @Override + DBRecord updateSettingsRecord(long associationId, String name, String strValue, long longValue) + throws IOException { + throw new ReadOnlyException(); } @Override @@ -96,4 +163,38 @@ class SettingsDBAdapterV0 extends SettingsDBAdapter { return settingsTable.getRecordCount(); } + @Override + RecordIterator getRecords() throws IOException { + return new TranslatedRecordIterator(settingsTable.iterator(), r -> translateV0Record(r)); + } + + @Override + RecordIterator getRecords(long minAssociationId, long maxAssociationId) throws IOException { + return settingsTable.indexIterator(V0_SETTINGS_ASSOCIATION_ID_COL, new LongField(minAssociationId), + new LongField(maxAssociationId), true); + } + + @Override + void delete(long minAssociationId, long maxAssociationId, TaskMonitor monitor) throws CancelledException, IOException { + throw new ReadOnlyException(); + } + + private DBRecord translateV0Record(DBRecord rec) { + if (rec == null) { + return null; + } + DBRecord normalizedRecord = + SettingsDBAdapterV1.V1_SETTINGS_SCHEMA.createRecord(rec.getKey()); + normalizedRecord.setLongValue(SETTINGS_ASSOCIATION_ID_COL, + rec.getLongValue(V0_SETTINGS_ASSOCIATION_ID_COL)); + String name = rec.getString(V0_SETTINGS_NAME_COL); + normalizedRecord.setShortValue(SettingsDBAdapter.SETTINGS_NAME_INDEX_COL, + assignNameIndexValue(name)); + normalizedRecord.setLongValue(SettingsDBAdapter.SETTINGS_LONG_VALUE_COL, + rec.getLongValue(V0_SETTINGS_LONG_VALUE_COL)); + normalizedRecord.setString(SettingsDBAdapter.SETTINGS_STRING_VALUE_COL, + rec.getString(V0_SETTINGS_STRING_VALUE_COL)); + return normalizedRecord; + } + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDBAdapterV1.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDBAdapterV1.java new file mode 100644 index 0000000000..3f3326aa5d --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDBAdapterV1.java @@ -0,0 +1,297 @@ +/* ### + * 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.program.database.data; + +import java.io.IOException; +import java.util.*; + +import db.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.VersionException; +import ghidra.util.task.TaskMonitor; + +/** + * Version 1 implementation for the accessing the data type settings database table. + * This version stores settings name as an index in each record which corresponds + * to an entry in the into a second table for + */ +class SettingsDBAdapterV1 extends SettingsDBAdapter { + + private final short MIN_NAME_INDEX = 1; // first assigned name index value + + // Default Settings Columns + static final int V1_SETTINGS_ASSOCIATION_ID_COL = 0; // e.g., Address-key, Datatype-ID + static final int V1_SETTINGS_NAME_INDEX_COL = 1; // short + static final int V1_SETTINGS_LONG_VALUE_COL = 2; + static final int V1_SETTINGS_STRING_VALUE_COL = 3; + + private static int V1_SCHEMA_VERSION = 1; + private static int V1_NAMES_SCHEMA_VERSION = 1; + + static final Schema V1_SETTINGS_SCHEMA = new Schema(V1_SCHEMA_VERSION, "SettingsID", + new Field[] { LongField.INSTANCE, ShortField.INSTANCE, LongField.INSTANCE, + StringField.INSTANCE }, + new String[] { "AssociationID", "Settings Name Index", "Long Value", "String Value" }); + + static final Schema V1_NAME_TABLE_SCHEMA = new Schema(V1_NAMES_SCHEMA_VERSION, "NameIndex", + new Field[] { StringField.INSTANCE }, + new String[] { "Settings Name" }); + + static final int V1_NAME_COL = 0; + + private Table settingsTable; + private Table settingsNameTable; + + private HashMap nameIndexMap = new HashMap<>(); + private HashMap nameStringMap = new HashMap<>(); + + SettingsDBAdapterV1(String tableName, DBHandle handle, boolean create) + throws VersionException, IOException { + String nameTableName = tableName + " Names"; + if (create) { + settingsTable = + handle.createTable(tableName, V1_SETTINGS_SCHEMA, + new int[] { V1_SETTINGS_ASSOCIATION_ID_COL }); + settingsNameTable = + handle.createTable(nameTableName, V1_NAME_TABLE_SCHEMA); + } + else { + settingsTable = handle.getTable(tableName); + if (settingsTable == null) { + throw new VersionException("Missing Table: " + tableName); + } + int ver = settingsTable.getSchema().getVersion(); + if (ver != V1_SCHEMA_VERSION) { + throw new VersionException(ver < V1_SCHEMA_VERSION); + } + settingsNameTable = handle.getTable(nameTableName); + if (settingsNameTable == null || + settingsNameTable.getSchema().getVersion() != V1_NAMES_SCHEMA_VERSION) { + throw new VersionException("Missing expected table: " + nameTableName); + } + } + } + + @Override + String getTableName() { + return settingsTable.getName(); + } + + @Override + DBRecord createSettingsRecord(long associationId, String name, String strValue, + long longValue) throws IOException { + + DBRecord record = V1_SETTINGS_SCHEMA.createRecord(settingsTable.getKey()); + record.setLongValue(V1_SETTINGS_ASSOCIATION_ID_COL, associationId); + record.setShortValue(V1_SETTINGS_NAME_INDEX_COL, assignNameIndexValue(name)); + record.setString(V1_SETTINGS_STRING_VALUE_COL, strValue); + record.setLongValue(V1_SETTINGS_LONG_VALUE_COL, longValue); + settingsTable.putRecord(record); + return record; + } + + @Override + public Field[] getSettingsKeys(long associationId) throws IOException { + return settingsTable.findRecords(new LongField(associationId), + V1_SETTINGS_ASSOCIATION_ID_COL); + } + + @Override + void removeAllSettingsRecords(long associationId) throws IOException { + for (Field key : getSettingsKeys(associationId)) { + removeSettingsRecord(key.getLongValue()); + } + } + + @Override + boolean removeSettingsRecord(long settingsID) throws IOException { + return settingsTable.deleteRecord(settingsID); + } + + @Override + boolean removeSettingsRecord(long associationId, String name) throws IOException { + short nameIndex = getNameIndex(name); + if (nameIndex < MIN_NAME_INDEX) { + return false; // no such name defined + } + for (Field key : getSettingsKeys(associationId)) { + DBRecord rec = settingsTable.getRecord(key); + if (nameIndex == rec.getShortValue(V1_SETTINGS_NAME_INDEX_COL)) { + settingsTable.deleteRecord(key); + return true; + } + } + return false; + } + + @Override + String[] getSettingsNames(long associationId) throws IOException { + ArrayList list = new ArrayList<>(); + for (Field key : getSettingsKeys(associationId)) { + DBRecord rec = settingsTable.getRecord(key); + list.add(getSettingName(rec)); + } + String[] names = new String[list.size()]; + return list.toArray(names); + } + + private void initNameMaps() throws IOException { + if (nameIndexMap != null) { + return; + } + nameIndexMap = new HashMap<>(); + nameStringMap = new HashMap<>(); + RecordIterator it = settingsNameTable.iterator(); + while (it.hasNext()) { + DBRecord nameRec = it.next(); + short nameIndex = (short) nameRec.getKey(); + String name = nameRec.getString(V1_NAME_COL); + nameIndexMap.put(nameIndex, name); + nameStringMap.put(name, nameIndex); + } + } + + private short assignNameIndexValue(String name) throws IOException { + initNameMaps(); + Short nameIndex = nameStringMap.get(name); + if (nameIndex != null) { + return nameIndex; + } + + // 1 is the first assigned name key value which allows for short cast + long key = Math.max(MIN_NAME_INDEX, settingsNameTable.getKey()); + if (key == Short.MAX_VALUE) { + // 32766 should be way more than enough unique setting names + throw new IOException("Too many settings names defined"); + } + + // Add new name record + nameIndex = (short) key; + DBRecord nameRec = V1_NAME_TABLE_SCHEMA.createRecord(key); + nameRec.setString(V1_NAME_COL, name); + settingsNameTable.putRecord(nameRec); + + nameIndexMap.put(nameIndex, name); + nameStringMap.put(name, nameIndex); + + return nameIndex; + } + + @Override + String getSettingName(DBRecord normalizedRecord) throws IOException { + initNameMaps(); + short nameIndex = normalizedRecord.getShortValue(SettingsDBAdapter.SETTINGS_NAME_INDEX_COL); + return nameIndexMap.get(nameIndex); + } + + @Override + void invalidateNameCache() { + nameIndexMap = null; + nameStringMap = null; + } + + /** + * Get previously assigned name index. + * @param name setting name + * @return name index or a value less than {@code #MIN_NAME_INDEX} if not defined + * @throws IOException if an IO error occurs + */ + private short getNameIndex(String name) throws IOException { + initNameMaps(); + Short index = nameStringMap.get(name); + if (index == null) { + return -1; // undefined name + } + return index; + } + + @Override + DBRecord getSettingsRecord(long settingsID) throws IOException { + return settingsTable.getRecord(settingsID); + } + + @Override + DBRecord getSettingsRecord(long associationId, String name) throws IOException { + short nameIndex = getNameIndex(name); + if (nameIndex < MIN_NAME_INDEX) { + return null; // not found - name not defined + } + for (Field key : getSettingsKeys(associationId)) { + DBRecord rec = settingsTable.getRecord(key); + if (rec.getShortValue(V1_SETTINGS_NAME_INDEX_COL) == nameIndex) { + return rec; + } + } + return null; + } + + @Override + void updateSettingsRecord(DBRecord record) throws IOException { + if (getSettingName(record) == null) { + throw new IOException("Record refers to invalid setting name index value"); + } + settingsTable.putRecord(record); + } + + @Override + DBRecord updateSettingsRecord(long associationId, String name, String strValue, long longValue) + throws IOException { + + DBRecord record = getSettingsRecord(associationId, name); + if (record == null) { + return createSettingsRecord(associationId, name, strValue, longValue); + } + + String recStrValue = record.getString(V1_SETTINGS_STRING_VALUE_COL); + long recLongValue = record.getLongValue(V1_SETTINGS_LONG_VALUE_COL); + + if (recLongValue != longValue || !Objects.equals(recStrValue, strValue)) { + record.setString(V1_SETTINGS_STRING_VALUE_COL, strValue); + record.setLongValue(V1_SETTINGS_LONG_VALUE_COL, longValue); + settingsTable.putRecord(record); + return record; + } + return null; + } + + @Override + int getRecordCount() { + return settingsTable.getRecordCount(); + } + + @Override + RecordIterator getRecords() throws IOException { + return settingsTable.iterator(); + } + + @Override + RecordIterator getRecords(long minAssociationId, long maxAssociationId) throws IOException { + return settingsTable.indexIterator(V1_SETTINGS_ASSOCIATION_ID_COL, + new LongField(minAssociationId), + new LongField(maxAssociationId), true); + } + + @Override + void delete(long minAssociationId, long maxAssociationId, TaskMonitor monitor) throws CancelledException, IOException { + DBFieldIterator it = settingsTable.indexKeyIterator(V1_SETTINGS_ASSOCIATION_ID_COL, + new LongField(minAssociationId), new LongField(maxAssociationId), true); + while (it.hasNext()) { + monitor.checkCanceled(); + settingsTable.deleteRecord(it.next()); + } + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDBManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDBManager.java deleted file mode 100644 index 43308626e6..0000000000 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/SettingsDBManager.java +++ /dev/null @@ -1,337 +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.program.database.data; - -import java.io.IOException; -import java.util.*; - -import db.DBRecord; -import db.Field; -import ghidra.docking.settings.Settings; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.DataTypeComponent; - -/** - * Database implementation for settings. - * - * - */ -class SettingsDBManager implements Settings { - - private DataTypeManagerDB dataMgr; - private long dataTypeID; - private SettingsDBAdapter adapter; - private DataType dataType; - private DataTypeComponent dtc; - - /** - * Constructor - * - */ - public SettingsDBManager(DataTypeManagerDB dataMgr, DataType dataType, long dataTypeID) { - - this.dataMgr = dataMgr; - this.dataType = dataType; - this.dataTypeID = dataTypeID; - adapter = dataMgr.getSettingsAdapter(); - } - - /** - * Constructor - * - */ - public SettingsDBManager(DataTypeManagerDB dataMgr, DataTypeComponent dtc, long dataTypeID) { - - this.dataMgr = dataMgr; - this.dtc = dtc; - this.dataType = dtc.getDataType(); - this.dataTypeID = dataTypeID; - adapter = dataMgr.getSettingsAdapter(); - } - - private void settingsChanged() { - // NOTE: There is currently no merge support for settings so recording within - // domain object change set is unneccessary - if (dtc != null) { - dataMgr.dataTypeChanged(dtc.getParent(), true); - } - else { - dataMgr.dataTypeChanged(dataType, true); - } - } - - @Override - public Long getLong(String name) { - SettingsDB settingsDB = getSettingsDB(name); - if (settingsDB != null) { - return settingsDB.getLongValue(); - } - if (dtc != null) { - return dataType.getDefaultSettings().getLong(name); - } - return null; - } - - @Override - public String getString(String name) { - SettingsDB settingsDB = getSettingsDB(name); - if (settingsDB != null) { - return settingsDB.getStringValue(); - } - if (dtc != null) { - return dataType.getDefaultSettings().getString(name); - } - return null; - } - - @Override - public byte[] getByteArray(String name) { - SettingsDB settingsDB = getSettingsDB(name); - if (settingsDB != null) { - return settingsDB.getByteValue(); - } - if (dtc != null) { - return dataType.getDefaultSettings().getByteArray(name); - } - return null; - } - - @Override - public Object getValue(String name) { - SettingsDB settingsDB = getSettingsDB(name); - if (settingsDB != null) { - return settingsDB.getValue(); - } - if (dtc != null) { - return dataType.getDefaultSettings().getValue(name); - } - return null; - } - - @Override - public void setLong(String name, long value) { - try { - if (updateSettingsRecord(name, null, value, null)) { - settingsChanged(); - } - } - catch (IOException e) { - dataMgr.dbError(e); - } - - } - - @Override - public void setString(String name, String value) { - - try { - if (updateSettingsRecord(name, value, -1, null)) { - settingsChanged(); - } - } - catch (IOException e) { - dataMgr.dbError(e); - } - } - - @Override - public void setByteArray(String name, byte[] value) { - try { - if (updateSettingsRecord(name, null, -1, value)) { - settingsChanged(); - } - } - catch (IOException e) { - dataMgr.dbError(e); - } - } - - @Override - public void setValue(String name, Object value) { - if (value instanceof Long) { - setLong(name, ((Long) value).longValue()); - } - else if (value instanceof String) { - setString(name, (String) value); - } - else if (value instanceof byte[]) { - setByteArray(name, (byte[]) value); - } - else { - throw new IllegalArgumentException("Value is not a known settings type"); - } - } - - @Override - public void clearSetting(String name) { - - try { - Field[] keys = adapter.getSettingsKeys(dataTypeID); - for (Field key : keys) { - DBRecord rec = adapter.getSettingsRecord(key.getLongValue()); - String settingsName = rec.getString(SettingsDBAdapter.SETTINGS_NAME_COL); - if (settingsName.equals(name)) { - adapter.removeSettingsRecord(key.getLongValue()); - settingsChanged(); - return; - } - } - } - catch (IOException e) { - dataMgr.dbError(e); - } - } - - @Override - public void clearAllSettings() { - try { - Field[] keys = adapter.getSettingsKeys(dataTypeID); - for (Field key : keys) { - adapter.removeSettingsRecord(key.getLongValue()); - } - settingsChanged(); - } - catch (IOException e) { - dataMgr.dbError(e); - } - } - - @Override - public String[] getNames() { - List list = new ArrayList(); - try { - Field[] keys = adapter.getSettingsKeys(dataTypeID); - for (Field key : keys) { - DBRecord rec = adapter.getSettingsRecord(key.getLongValue()); - String name = rec.getString(SettingsDBAdapter.SETTINGS_NAME_COL); - if (!list.contains(name)) { - list.add(name); - } - } - String[] names = new String[list.size()]; - return list.toArray(names); - } - catch (IOException e) { - dataMgr.dbError(e); - } - return new String[0]; - } - - @Override - public boolean isEmpty() { - try { - return adapter.getSettingsKeys(dataTypeID).length == 0; - } - catch (IOException e) { - dataMgr.dbError(e); - } - return true; - } - - void update(Settings settings) { - clearAllSettings(); - String[] names = settings.getNames(); - for (String name : names) { - setValue(name, settings.getValue(name)); - } - } - - /** - * Following settings value change compare old and new values and return - * true if old and new values are different. - * @param oldStrValue old settings string value - * @param newStrValue new settings string value - * @param oldByteValue old settings byte array value - * @param newByteValue new settings byte array value - * @param oldLongValue old settings long value - * @param newLongValue new settings long value - * @return true true if value change detected else false - */ - static boolean valuesChanged(String oldStrValue, String newStrValue, byte[] oldByteValue, - byte[] newByteValue, long oldLongValue, long newLongValue) { - - if ((oldStrValue != null && !oldStrValue.equals(newStrValue)) || - (oldStrValue == null && newStrValue != null)) { - return true; - } - if (oldByteValue != null && newByteValue != null) { - return Arrays.equals(oldByteValue, newByteValue); - } - if ((oldByteValue != null && newByteValue == null) || - (oldByteValue == null && newByteValue != null)) { - return true; - } - return oldLongValue != newLongValue; - } - - private DBRecord getRecord(String name) { - try { - Field[] keys = adapter.getSettingsKeys(dataTypeID); - for (Field key : keys) { - DBRecord rec = adapter.getSettingsRecord(key.getLongValue()); - if (rec.getString(SettingsDBAdapter.SETTINGS_NAME_COL).equals(name)) { - return rec; - } - } - } - catch (IOException e) { - dataMgr.dbError(e); - } - return null; - - } - - private SettingsDB getSettingsDB(String name) { - - DBRecord record = getRecord(name); - if (record != null) { - return new SettingsDB(record); - } - return null; - } - - private boolean updateSettingsRecord(String name, String strValue, long longValue, - byte[] byteValue) throws IOException { - - boolean wasChanged = false; - DBRecord record = getRecord(name); - if (record == null) { - wasChanged = true; - record = adapter.createSettingsRecord(dataTypeID, name, strValue, longValue, byteValue); - } - else { - String recStrValue = record.getString(SettingsDBAdapter.SETTINGS_STRING_VALUE_COL); - byte[] recByteValue = record.getBinaryData(SettingsDBAdapter.SETTINGS_BYTE_VALUE_COL); - long recLongValue = record.getLongValue(SettingsDBAdapter.SETTINGS_LONG_VALUE_COL); - - wasChanged = valuesChanged(recStrValue, strValue, recByteValue, byteValue, recLongValue, - longValue); - if (wasChanged) { - record.setString(SettingsDBAdapter.SETTINGS_STRING_VALUE_COL, strValue); - record.setLongValue(SettingsDBAdapter.SETTINGS_LONG_VALUE_COL, longValue); - record.setBinaryData(SettingsDBAdapter.SETTINGS_BYTE_VALUE_COL, byteValue); - adapter.updateSettingsRecord(record); - } - } - return wasChanged; - } - - @Override - public Settings getDefaultSettings() { - // This settings object already represents the default settings - return null; - } -} 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 e91b994a08..90ad808a11 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 @@ -554,7 +554,9 @@ class StructureDB extends CompositeDB implements StructureInternal { private DataTypeComponentDB doDelete(int index) throws IOException { DataTypeComponentDB dtc = components.remove(index); dtc.getDataType().removeParent(this); - componentAdapter.removeRecord(dtc.getKey()); + long compKey = dtc.getKey(); + componentAdapter.removeRecord(compKey); + dataMgr.getSettingsAdapter().removeAllSettingsRecords(compKey); return dtc; } @@ -882,14 +884,7 @@ class StructureDB extends CompositeDB implements StructureInternal { int idx = Collections.binarySearch(components, Integer.valueOf(ordinal), OrdinalComparator.INSTANCE); if (idx >= 0) { - DataTypeComponentDB dtc = components.remove(idx); - dtc.getDataType().removeParent(this); - try { - componentAdapter.removeRecord(dtc.getKey()); - } - catch (IOException e) { - dataMgr.dbError(e); - } + DataTypeComponentDB dtc = doDelete(idx); int len = dtc.getLength(); if (len > 1) { shiftOffsets(idx, len - 1, 0); // updates timestamp @@ -1589,7 +1584,9 @@ class StructureDB extends CompositeDB implements StructureInternal { } for (DataTypeComponentDB dtc : components) { dtc.getDataType().removeParent(this); - componentAdapter.removeRecord(dtc.getKey()); + long compKey = dtc.getKey(); + componentAdapter.removeRecord(compKey); + dataMgr.getSettingsAdapter().removeAllSettingsRecords(compKey); } components.clear(); @@ -1700,11 +1697,9 @@ class StructureDB extends CompositeDB implements StructureInternal { removeBitFieldComponent = bitfieldDt.getBaseDataType() == dt; } if (removeBitFieldComponent || dtc.getDataType() == dt) { - dt.removeParent(this); + doDelete(i); // FIXME: Consider replacing with undefined type instead of removing (don't remove bitfield) - components.remove(i); shiftOffsets(i, dtc.getLength() - 1, 0); // ordinals only - componentAdapter.removeRecord(dtc.getKey()); --numComponents; // may be revised by repack changed = true; } @@ -1861,7 +1856,7 @@ class StructureDB extends CompositeDB implements StructureInternal { return false; } - checkIsValid(); + validate(lock); if (resolving) { // actively resolving children if (dataType.getUniversalID().equals(getUniversalID())) { return true; @@ -2202,10 +2197,8 @@ class StructureDB extends CompositeDB implements StructureInternal { } if (remove) { // error case - remove component - oldDt.removeParent(this); - components.remove(i); + doDelete(i); shiftOffsets(i, comp.getLength() - 1, 0); // ordinals only - componentAdapter.removeRecord(comp.getKey()); changed = true; } } @@ -2224,7 +2217,7 @@ class StructureDB extends CompositeDB implements StructureInternal { } private void setComponentDataType(DataTypeComponentDB comp, DataType newDt, - int nextIndex) { + int nextIndex) throws IOException { int oldLen = comp.getLength(); int len = DataTypeComponent.usesZeroLengthComponent(newDt) ? 0 : newDt.getLength(); @@ -2238,6 +2231,7 @@ class StructureDB extends CompositeDB implements StructureInternal { comp.setLength(len, false); // do before record save below } comp.setDataType(newDt); // saves component record + dataMgr.getSettingsAdapter().removeAllSettingsRecords(comp.getKey()); newDt.addParent(this); if (isPackingEnabled()) { @@ -2281,7 +2275,9 @@ class StructureDB extends CompositeDB implements StructureInternal { for (DataTypeComponentDB dtc : components) { dtc.getDataType().removeParent(this); try { - componentAdapter.removeRecord(dtc.getKey()); + long compKey = dtc.getKey(); + componentAdapter.removeRecord(compKey); + dataMgr.getSettingsAdapter().removeAllSettingsRecords(compKey); } catch (IOException e) { dataMgr.dbError(e); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/TypedefDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/TypedefDB.java index 9ca2cc1140..4f419875b8 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/TypedefDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/TypedefDB.java @@ -36,13 +36,17 @@ class TypedefDB extends DataTypeDB implements TypeDef { private SettingsDefinition[] settingsDef; /** - * Constructor - * @param key + * Construct TypeDefDB instance + * @param dataMgr datatype manager + * @param cache DataTypeDB cache + * @param adapter TypeDef record adapter + * @param record TypeDefDB record */ public TypedefDB(DataTypeManagerDB dataMgr, DBObjectCache cache, TypedefDBAdapter adapter, DBRecord record) { super(dataMgr, cache, record); this.adapter = adapter; + this.defaultSettings = null; // ensure lazy initialization } @Override @@ -98,9 +102,7 @@ class TypedefDB extends DataTypeDB implements TypeDef { @Override public String getRepresentation(MemBuffer buf, Settings settings, int length) { - checkIsValid(); - TypedefSettings ts = new TypedefSettings(super.getDefaultSettings(), settings); - return getDataType().getRepresentation(buf, ts, length); + return getDataType().getRepresentation(buf, settings, length); } @Override @@ -164,14 +166,18 @@ class TypedefDB extends DataTypeDB implements TypeDef { @Override public DataType clone(DataTypeManager dtm) { - return new TypedefDataType(getCategoryPath(), getName(), getDataType(), getUniversalID(), + TypedefDataType typeDef = + new TypedefDataType(getCategoryPath(), getName(), getDataType(), getUniversalID(), getSourceArchive(), getLastChangeTime(), getLastChangeTimeInSourceArchive(), dtm); - + TypedefDataType.copyTypeDefSettings(this, typeDef, false); + return typeDef; } @Override public DataType copy(DataTypeManager dtm) { - return new TypedefDataType(getCategoryPath(), getName(), getDataType(), dtm); + TypedefDataType typeDef = new TypedefDataType(getCategoryPath(), getName(), getDataType(), dtm); + TypedefDataType.copyTypeDefSettings(this, typeDef, false); + return typeDef; } @Override @@ -183,10 +189,13 @@ class TypedefDB extends DataTypeDB implements TypeDef { return false; } TypeDef td = (TypeDef) obj; - checkIsValid(); + validate(lock); if (!DataTypeUtilities.equalsIgnoreConflict(getName(), td.getName())) { return false; } + if (!hasSameTypeDefSettings(td)) { + return false; + } return DataTypeUtilities.isSameOrEquivalentDataType(getDataType(), td.getDataType()); } @@ -204,6 +213,8 @@ class TypedefDB extends DataTypeDB implements TypeDef { lock.acquire(); try { if (checkIsValid() && getDataType() == oldDt) { + settingsDef = null; + defaultSettings = null; oldDt.removeParent(this); newDt = resolve(newDt); newDt.addParent(this); @@ -240,6 +251,7 @@ class TypedefDB extends DataTypeDB implements TypeDef { @Override public void dataTypeNameChanged(DataType dt, String oldName) { + // ignore } @Override @@ -253,8 +265,8 @@ class TypedefDB extends DataTypeDB implements TypeDef { try { DBRecord rec = adapter.getRecord(key); if (rec != null) { + settingsDef = null; record = rec; -// super.getDefaultSettings(); // not sure why it was doing this - no one else does. return super.refresh(); } } @@ -271,7 +283,15 @@ class TypedefDB extends DataTypeDB implements TypeDef { checkIsValid(); if (settingsDef == null) { DataType dt = getDataType(); - settingsDef = dt.getSettingsDefinitions(); + SettingsDefinition[] settingsDefinitions = dt.getSettingsDefinitions(); + TypeDefSettingsDefinition[] typeDefSettingsDefinitions = + dt.getTypeDefSettingsDefinitions(); + settingsDef = new SettingsDefinition[settingsDefinitions.length + + typeDefSettingsDefinitions.length]; + System.arraycopy(settingsDefinitions, 0, settingsDef, 0, + settingsDefinitions.length); + System.arraycopy(typeDefSettingsDefinitions, 0, settingsDef, + settingsDefinitions.length, typeDefSettingsDefinitions.length); } return settingsDef; } @@ -280,6 +300,34 @@ class TypedefDB extends DataTypeDB implements TypeDef { } } + @Override + public TypeDefSettingsDefinition[] getTypeDefSettingsDefinitions() { + return getDataType().getTypeDefSettingsDefinitions(); + } + + protected Settings doGetDefaultSettings() { + DataTypeSettingsDB settings = new DataTypeSettingsDB(dataMgr, this, key); + settings.setLock(dataMgr instanceof BuiltInDataTypeManager); + settings.setAllowedSettingPredicate(n -> isAllowedSetting(n)); + settings.setDefaultSettings(getDataType().getDefaultSettings()); + return settings; + } + + private boolean isAllowedSetting(String settingName) { + if (dataMgr instanceof ProgramBasedDataTypeManager) { + // any setting permitted within a program DTM + return true; + } + // non-TypeDefSettingsDefinition settings are not permitted in non-program DTM + // since they will be discarded during resolve and ignored for equivalence checks + for (TypeDefSettingsDefinition def : getTypeDefSettingsDefinitions()) { + if (def.getStorageKey().equals(settingName)) { + return true; + } + } + return false; + } + @Override public String toString() { return "typedef " + this.getName() + " " + getDataType().getName(); @@ -384,8 +432,17 @@ class TypedefDB extends DataTypeDB implements TypeDef { if (!(dataType instanceof TypeDef)) { throw new UnsupportedOperationException(); } - if (dataType != this) { - dataTypeReplaced(getDataType(), ((TypeDef) dataType).getDataType()); + if (dataType == this) { + return; + } + lock.acquire(); + try { + TypeDef td = (TypeDef) dataType; + dataTypeReplaced(getDataType(), td.getDataType()); + TypedefDataType.copyTypeDefSettings(td, this, true); + } + finally { + lock.release(); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/TypedefSettings.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/TypedefSettings.java deleted file mode 100644 index 2bd73a6e6b..0000000000 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/TypedefSettings.java +++ /dev/null @@ -1,158 +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.program.database.data; - -import java.util.ArrayList; -import java.util.List; - -import ghidra.docking.settings.Settings; - -/** - * Settings for typedefs that combines the default settings with instance - * settings if instance settings for a setting name does not exist. - * - * - * - */ -class TypedefSettings implements Settings { - - private Settings defaultSettings; - private Settings instanceSettings; - - TypedefSettings(Settings defaultSettings, Settings instanceSettings) { - this.defaultSettings = defaultSettings; - this.instanceSettings = instanceSettings; - } - - /* (non-Javadoc) - * @see ghidra.program.model.data.Settings#clearAllSettings() - */ - public void clearAllSettings() { - defaultSettings.clearAllSettings(); - } - - /* (non-Javadoc) - * @see ghidra.program.model.data.Settings#clearSetting(java.lang.String) - */ - public void clearSetting(String name) { - defaultSettings.clearSetting(name); - } - - /* (non-Javadoc) - * @see ghidra.program.model.data.Settings#getByteArray(java.lang.String) - */ - public byte[] getByteArray(String name) { - byte[] b = instanceSettings.getByteArray(name); - if (b == null) { - b = defaultSettings.getByteArray(name); - } - return b; - } - - /* (non-Javadoc) - * @see ghidra.program.model.data.Settings#getLong(java.lang.String) - */ - public Long getLong(String name) { - Long value = instanceSettings.getLong(name); - if (value == null) { - value = defaultSettings.getLong(name); - } - return value; - } - - /* (non-Javadoc) - * @see ghidra.program.model.data.Settings#getNames() - */ - public String[] getNames() { - List list = new ArrayList(); - String[] instNames = instanceSettings.getNames(); - for (int i = 0; i < instNames.length; i++) { - list.add(instNames[i]); - } - String[] defNames = defaultSettings.getNames(); - for (int i = 0; i < defNames.length; i++) { - if (!list.contains(defNames[i])) { - list.add(defNames[i]); - } - } - String[] names = new String[list.size()]; - return list.toArray(names); - } - - /* (non-Javadoc) - * @see ghidra.program.model.data.Settings#getString(java.lang.String) - */ - public String getString(String name) { - String value = instanceSettings.getString(name); - if (value == null) { - value = defaultSettings.getString(name); - } - return value; - } - - /* (non-Javadoc) - * @see ghidra.program.model.data.Settings#getValue(java.lang.String) - */ - public Object getValue(String name) { - Object value = instanceSettings.getValue(name); - if (value == null) { - value = defaultSettings.getValue(name); - } - return value; - } - - /* (non-Javadoc) - * @see ghidra.program.model.data.Settings#isEmpty() - */ - public boolean isEmpty() { - return instanceSettings.isEmpty() && defaultSettings.isEmpty(); - } - - /* (non-Javadoc) - * @see ghidra.program.model.data.Settings#setByteArray(java.lang.String, byte[]) - */ - public void setByteArray(String name, byte[] value) { - defaultSettings.setByteArray(name, value); - } - - /* (non-Javadoc) - * @see ghidra.program.model.data.Settings#setLong(java.lang.String, long) - */ - public void setLong(String name, long value) { - defaultSettings.setLong(name, value); - } - - /* (non-Javadoc) - * @see ghidra.program.model.data.Settings#setString(java.lang.String, java.lang.String) - */ - public void setString(String name, String value) { - defaultSettings.setString(name, value); - } - - /* (non-Javadoc) - * @see ghidra.program.model.data.Settings#setValue(java.lang.String, java.lang.Object) - */ - public void setValue(String name, Object value) { - defaultSettings.setValue(name, value); - } - - /** - * @see ghidra.docking.settings.Settings#getDefaultSettings() - */ - public Settings getDefaultSettings() { - return defaultSettings; - } -} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/UnionDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/UnionDB.java index da516ddd99..95c99b23ac 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/UnionDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/UnionDB.java @@ -161,6 +161,7 @@ class UnionDB extends CompositeDB implements UnionInternal { private void removeComponent(long compKey) { try { componentAdapter.removeRecord(compKey); + dataMgr.getSettingsAdapter().removeAllSettingsRecords(compKey); } catch (IOException e) { dataMgr.dbError(e); @@ -726,7 +727,7 @@ class UnionDB extends CompositeDB implements UnionInternal { return false; } - checkIsValid(); + validate(lock); if (resolving) { // actively resolving children if (dataType.getUniversalID().equals(getUniversalID())) { return true; @@ -830,6 +831,7 @@ class UnionDB extends CompositeDB implements UnionInternal { oldDt.removeParent(this); dtc.setLength(len, false); dtc.setDataType(replacementDt); // updates record + dataMgr.getSettingsAdapter().removeAllSettingsRecords(dtc.getKey()); replacementDt.addParent(this); changed = true; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AbstractDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AbstractDataType.java index 34eb720e06..3fa67ebacd 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AbstractDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AbstractDataType.java @@ -29,6 +29,10 @@ import ghidra.util.exception.DuplicateNameException; * classes can be created without implementing too many methods. */ public abstract class AbstractDataType implements DataType { + + private final static TypeDefSettingsDefinition[] EMPTY_TYPEDEF_DEFINITIONS = + new TypeDefSettingsDefinition[0]; + protected String name; protected CategoryPath categoryPath; protected final DataTypeManager dataMgr; @@ -51,6 +55,11 @@ public abstract class AbstractDataType implements DataType { this.dataMgr = dataTypeManager; } + @Override + public TypeDefSettingsDefinition[] getTypeDefSettingsDefinitions() { + return EMPTY_TYPEDEF_DEFINITIONS; + } + @Override public CategoryPath getCategoryPath() { return categoryPath; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AbstractPointerTypedefDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AbstractPointerTypedefDataType.java new file mode 100644 index 0000000000..4f121ff6e5 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AbstractPointerTypedefDataType.java @@ -0,0 +1,196 @@ +/* ### + * 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.program.model.data; + +import org.apache.commons.lang3.StringUtils; + +import ghidra.docking.settings.Settings; +import ghidra.docking.settings.SettingsDefinition; +import ghidra.program.database.data.DataTypeUtilities; +import ghidra.program.model.mem.MemBuffer; +import ghidra.util.UniversalID; +import ghidra.util.UniversalIdGenerator; + +/** + * AbstractPointerTypedefDataType provides an abstract implementation for + * a Pointer-Typedef BuiltIn datatype. + *
    + * If a generated name is used the name will be locked-in once this datatype is resolved + * and will not automatcally update if any subsequent changes are made to + * {@link TypeDefSettingsDefinition} settings or the name of the referenced datatype. + */ +public abstract class AbstractPointerTypedefDataType extends BuiltIn implements TypeDef { + + private String typedefName; + private TypedefDataType modelTypedef; + private UniversalID universalId = UniversalIdGenerator.nextID(); + + /** + * Constructs a pointer-typedef. The category path will match that of the + * referencedDataType. Subclass may set various default settings which correspond to + * {@link PointerTypeSettingsDefinition}. + * @param name name of this pointer-typedef or null to force auto-name generation. + * @param referencedDataType data type this pointer points to + * @param pointerSize pointer size in bytes or -1 for default pointer size + * @param dtm data-type manager whose data organization should be used + */ + public AbstractPointerTypedefDataType(String name, DataType referencedDataType, + int pointerSize, DataTypeManager dtm) { + super(getCategoryPath(referencedDataType), getTempNameIfNeeded(name), dtm); + setTypedefName(name); + modelTypedef = + new TypedefDataType("TEMP", new PointerDataType(referencedDataType, pointerSize, dtm)); + } + + /** + * Constructs a pointer-typedef. The category path will match that of the + * pointerDataType. Subclass may set various default settings which correspond to + * {@link PointerTypeSettingsDefinition}. + * @param name name of this pointer-typedef or null to force auto-name generation. + * @param pointerDataType associated pointer datatype (required) + * @param dtm data-type manager whose data organization should be used + */ + public AbstractPointerTypedefDataType(String name, Pointer pointerDataType, + DataTypeManager dtm) { + super(pointerDataType.getCategoryPath(), getTempNameIfNeeded(name), dtm); + setTypedefName(name); + modelTypedef = new TypedefDataType("TEMP", pointerDataType.clone(dtm)); + } + + public boolean isAutoNamed() { + return typedefName == null; + } + + private static CategoryPath getCategoryPath(DataType referencedDataType) { + return referencedDataType != null ? referencedDataType.getCategoryPath() + : CategoryPath.ROOT; + } + + private static String getTempNameIfNeeded(String baseName) { + return StringUtils.isBlank(baseName) ? "TEMP" : baseName; + } + + /** + * Get the referenced datatype used to construct this datatype + * (datatype which pointer references). + * @return referenced datatype + */ + protected DataType getReferencedDataType() { + Pointer ptrType = (Pointer) getDataType(); + return ptrType.getDataType(); + } + + public UniversalID getUniversalID() { + return universalId; + } + + protected boolean hasGeneratedNamed() { + return (typedefName == null); + } + + void setTypedefName(String name) { + if (name != null) { + throw new IllegalArgumentException("Invalid DataType name: " + name); + } + this.typedefName = name; + } + + @Override + public boolean isEquivalent(DataType obj) { + if (obj == this) { + return true; + } + if (obj == null || !(obj instanceof TypeDef)) { + return false; + } + TypeDef td = (TypeDef) obj; + if (!DataTypeUtilities.equalsIgnoreConflict(getName(), td.getName())) { + return false; + } + if (!hasSameTypeDefSettings(td)) { + return false; + } + return DataTypeUtilities.isSameOrEquivalentDataType(getDataType(), td.getDataType()); + } + + @Override + public String getName() { + if (typedefName == null) { + // Do not cache name since we do not have listeners to detect + // settings change which may impact name generation. + return TypedefDataType.generateTypedefName(this); + } + return typedefName; // use name provided at instantiation + } + + @Override + public boolean hasLanguageDependantLength() { + return modelTypedef.hasLanguageDependantLength(); + } + + @Override + public int getLength() { + return modelTypedef.getLength(); + } + + @Override + public DataType getDataType() { + return modelTypedef.getDataType(); + } + + @Override + public DataType getBaseDataType() { + return modelTypedef.getBaseDataType(); + } + + @Override + public SettingsDefinition[] getBuiltInSettingsDefinitions() { + return getTypeDefSettingsDefinitions(); + } + + @Override + public TypeDefSettingsDefinition[] getTypeDefSettingsDefinitions() { + return modelTypedef.getTypeDefSettingsDefinitions(); + } + + @Override + public Settings getDefaultSettings() { + return modelTypedef.getDefaultSettings(); + } + + @Override + public boolean dependsOn(DataType dt) { + DataType myDt = getDataType(); + return (myDt == dt || myDt.dependsOn(dt)); + } + + @Override + public String toString() { + return getClass().getSimpleName() + ": typedef " + getName() + " " + + getDataType().getName(); + } + + @Override + public Object getValue(MemBuffer buf, Settings settings, int length) { + return modelTypedef.getValue(buf, settings, length); + } + + @Override + public String getRepresentation(MemBuffer buf, Settings settings, int length) { + return modelTypedef.getRepresentation(buf, settings, length); + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AddressSpaceSettingsDefinition.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AddressSpaceSettingsDefinition.java new file mode 100644 index 0000000000..2b45262412 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AddressSpaceSettingsDefinition.java @@ -0,0 +1,104 @@ +/* ### + * 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.program.model.data; + +import org.apache.commons.lang3.StringUtils; + +import ghidra.docking.settings.Settings; +import ghidra.docking.settings.StringSettingsDefinition; + +public class AddressSpaceSettingsDefinition + implements StringSettingsDefinition, TypeDefSettingsDefinition { + + private static final String ADDRESS_SPACE_SETTING_NAME = "addr_space_name"; + private static final String DESCRIPTION = + "Identifies the referenced address space name (case-sensitive; ignored if no match)"; + private static final String DISPLAY_NAME = "Address Space"; + + private static final String DEFAULT = null; + + public static final AddressSpaceSettingsDefinition DEF = new AddressSpaceSettingsDefinition(); + + private AddressSpaceSettingsDefinition() { + } + + @Override + public String getValue(Settings settings) { + if (settings == null) { + return DEFAULT; + } + String value = settings.getString(ADDRESS_SPACE_SETTING_NAME); + if (value == null) { + return DEFAULT; + } + return value; + } + + @Override + public void setValue(Settings settings, String value) { + if (StringUtils.isBlank(value)) { + settings.clearSetting(ADDRESS_SPACE_SETTING_NAME); + } + else { + settings.setString(ADDRESS_SPACE_SETTING_NAME, value); + } + } + + @Override + public boolean hasValue(Settings settings) { + return getValue(settings) != DEFAULT; + } + + @Override + public String getName() { + return DISPLAY_NAME; + } + + @Override + public String getStorageKey() { + return ADDRESS_SPACE_SETTING_NAME; + } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public void clear(Settings settings) { + settings.clearSetting(ADDRESS_SPACE_SETTING_NAME); + } + + @Override + public void copySetting(Settings srcSettings, Settings destSettings) { + String value = srcSettings.getString(ADDRESS_SPACE_SETTING_NAME); + if (value == null) { + destSettings.clearSetting(ADDRESS_SPACE_SETTING_NAME); + } + else { + destSettings.setString(ADDRESS_SPACE_SETTING_NAME, value); + } + } + + @Override + public String getAttributeSpecification(Settings settings) { + String spaceName = getValue(settings); + if (!StringUtils.isBlank(spaceName)) { + return "space(" + spaceName + ")"; + } + return null; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AlignedStructureInspector.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AlignedStructureInspector.java index 2c00efbdcc..54bcd669cb 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AlignedStructureInspector.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/AlignedStructureInspector.java @@ -116,11 +116,6 @@ public class AlignedStructureInspector extends AlignedStructurePacker { return component.getDefaultSettings(); } - @Override - public void setDefaultSettings(Settings settings) { - throw new UnsupportedOperationException(); - } - @Override public void setComment(String comment) { throw new UnsupportedOperationException(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ArrayDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ArrayDataType.java index cb2e6ef7ab..f16b2e739d 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ArrayDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ArrayDataType.java @@ -119,6 +119,11 @@ public class ArrayDataType extends DataTypeImpl implements Array { return getDataType().getSettingsDefinitions(); } + @Override + public TypeDefSettingsDefinition[] getTypeDefSettingsDefinitions() { + return getDataType().getTypeDefSettingsDefinitions(); + } + @Override public boolean isEquivalent(DataType obj) { if (obj == this) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldDataType.java index ebfbbe168f..9a3d09b951 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BitFieldDataType.java @@ -427,11 +427,6 @@ public class BitFieldDataType extends AbstractDataType { return intDT.getRepresentation(big, settings, effectiveBitSize); } - @Override - public void setDefaultSettings(Settings settings) { - this.defaultSettings = settings; - } - @Override public int getAlignment() { return baseDataType.getAlignment(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltIn.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltIn.java index 7595f3ba59..b0cde32c7a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltIn.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltIn.java @@ -16,6 +16,7 @@ package ghidra.program.model.data; import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive; +import ghidra.docking.settings.Settings; import ghidra.docking.settings.SettingsDefinition; import ghidra.program.model.lang.DecompilerLanguage; import ghidra.util.InvalidNameException; @@ -29,6 +30,8 @@ import ghidra.util.exception.DuplicateNameException; * Base class for built-in Datatypes. A built-in data type is * searched for in the classpath and added automatically to the available * data types in the data type manager. + * + * NOTE: Settings are immutable when a DataTypeManager has not been specified (i.e., null). */ public abstract class BuiltIn extends DataTypeImpl implements BuiltInDataType { @@ -73,6 +76,11 @@ public abstract class BuiltIn extends DataTypeImpl implements BuiltInDataType { return null; } + @Override + public void setDefaultSettings(Settings settings) { + defaultSettings = settings; + } + @Override public boolean isEquivalent(DataType dt) { if (dt == this) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltInDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltInDataType.java index 0a5136803f..b20be5ac21 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltInDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltInDataType.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +15,7 @@ */ package ghidra.program.model.data; +import ghidra.docking.settings.Settings; import ghidra.util.classfinder.ExtensionPoint; /** @@ -38,4 +38,13 @@ public interface BuiltInDataType extends DataType, ExtensionPoint { */ public String getCTypeDeclaration(DataOrganization dataOrganization); + /** + * Set the default settings for this data type. + *
    + * NOTE: This method is reserved for internal DB use. + *
    + * @param settings the settings to be used as this dataTypes default settings. + */ + public void setDefaultSettings(Settings settings); + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/CharsetSettingsDefinition.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/CharsetSettingsDefinition.java index a64e159539..42892bd606 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/CharsetSettingsDefinition.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/CharsetSettingsDefinition.java @@ -71,6 +71,11 @@ public class CharsetSettingsDefinition implements EnumSettingsDefinition { return (cs != null) ? cs : defaultValue; } + @Override + public String getValueString(Settings settings) { + return getCharset(settings, null); + } + private String getDeprecatedEncodingValue(Settings settings) { Long langIndex = settings.getLong(DEPRECATED_LANGUAGE_SETTING_NAME); Long encodingIndex = settings.getLong(DEPRECATED_ENCODING_SETTING_NAME); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ComponentOffsetSettingsDefinition.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ComponentOffsetSettingsDefinition.java new file mode 100644 index 0000000000..60d80d5c49 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ComponentOffsetSettingsDefinition.java @@ -0,0 +1,122 @@ +/* ### + * 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.program.model.data; + +import java.math.BigInteger; + +import ghidra.docking.settings.NumberSettingsDefinition; +import ghidra.docking.settings.Settings; + +public class ComponentOffsetSettingsDefinition + implements NumberSettingsDefinition, TypeDefSettingsDefinition { + + private static final String COMPONENT_OFFSET_SETTING_NAME = "component_offset"; + private static final String DESCRIPTION = + "Identifies a component offset to be applied to a pointer reference"; + private static final String DISPLAY_NAME = "Component Offset"; + private static BigInteger MAX_VALUE = BigInteger.valueOf(Long.MAX_VALUE); + + private static final int DEFAULT = 0; + + public static final ComponentOffsetSettingsDefinition DEF = + new ComponentOffsetSettingsDefinition(); + + private ComponentOffsetSettingsDefinition() { + } + + @Override + public BigInteger getMaxValue() { + return MAX_VALUE; + } + + @Override + public boolean allowNegativeValue() { + return true; + } + + @Override + public boolean isHexModePreferred() { + return false; + } + + @Override + public long getValue(Settings settings) { + if (settings == null) { + return DEFAULT; + } + Long value = settings.getLong(COMPONENT_OFFSET_SETTING_NAME); + if (value == null) { + return DEFAULT; + } + return value; + } + + @Override + public void setValue(Settings settings, long value) { + if (value == DEFAULT) { + settings.clearSetting(COMPONENT_OFFSET_SETTING_NAME); + } + else { + settings.setLong(COMPONENT_OFFSET_SETTING_NAME, value); + } + } + + @Override + public boolean hasValue(Settings settings) { + return getValue(settings) != DEFAULT; + } + + @Override + public String getName() { + return DISPLAY_NAME; + } + + @Override + public String getStorageKey() { + return COMPONENT_OFFSET_SETTING_NAME; + } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public void clear(Settings settings) { + settings.clearSetting(COMPONENT_OFFSET_SETTING_NAME); + } + + @Override + public void copySetting(Settings srcSettings, Settings destSettings) { + Long value = srcSettings.getLong(COMPONENT_OFFSET_SETTING_NAME); + if (value == null) { + destSettings.clearSetting(COMPONENT_OFFSET_SETTING_NAME); + } + else { + destSettings.setLong(COMPONENT_OFFSET_SETTING_NAME, value); + } + } + + @Override + public String getAttributeSpecification(Settings settings) { + if (hasValue(settings)) { + long offset = getValue(settings); + return "offset(" + Long.toString(offset) + ")"; + } + return null; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataType.java index f3ad09b423..883e292c25 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataType.java @@ -63,16 +63,41 @@ public interface DataType { public boolean hasLanguageDependantLength(); /** - * Gets a list of all the settingsDefinitions used by this datatype. + * Get the list of settings definitions available for use with this datatype. + *

    + * In the case of a {@link TypeDef}, the return list will include the + * {@link TypeDefSettingsDefinition} list from the associated base data type. + *

    + * Unlike {@link TypeDefSettingsDefinition} standard settings definitions + * generally support default, component-default and data-instance use. + * In addition, standard settings definitions are never considered during + * {@link #isEquivalent(DataType)} checking or during the resolve process. * - * @return a list of the settingsDefinitions used by this datatype. + * @return list of the settings definitions for this datatype. */ public SettingsDefinition[] getSettingsDefinitions(); /** - * Gets the default settings for this datatype. + * Get the list of all settings definitions for this datatype that may be + * used for an associated {@link TypeDef}. When used for an associated + * {@link TypeDef}, these settings will be considered during a + * {@link TypeDef#isEquivalent(DataType)} check and will be preserved + * during the resolve process. * - * @return the default settings for this datatype. + * @return a list of the settings definitions for a {@link TypeDef} + * associated with this datatype. + */ + public TypeDefSettingsDefinition[] getTypeDefSettingsDefinitions(); + + /** + * Gets the settings for this data type. The settings may have underlying default settings + * and may in turn become defaults for instance-specific settings (e.g., Data or DataTypeComponent). + * It is important to note that these settings are tied to a specific DataType instantiation + * so it is important to understand the scope of its use. Example: The {@link BuiltInDataTypeManager} + * has its own set of DataType instances which are separate from those which have been instantiated + * or resolved to a specific Program/Archive {@link DataTypeManager}. Settings manipulation may + * be disabled by default in some instances. + * @return the settings for this dataType. */ public Settings getDefaultSettings(); @@ -448,20 +473,11 @@ public interface DataType { public void dataTypeReplaced(DataType oldDt, DataType newDt); /** - * Set the default settings for this datatype. - *

    - * TODO: This method is reserved for internal DB use.
    + * Inform this data type that it has the given parent + *
    + * TODO: This method is reserved for internal DB use. * - * @param settings the settings to be used as this dataTypes default settings. - */ - public void setDefaultSettings(Settings settings); - - /** - * Inform this datatype that it has the given parent - *

    - * TODO: This method is reserved for internal DB use.
    - * - * @param dt parent datatype + * @param dt parent data type */ public void addParent(DataType dt); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java index 9302356b6d..d80521721f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponent.java @@ -93,12 +93,6 @@ public interface DataTypeComponent { */ public Settings getDefaultSettings(); - /** - * Set default settings for this dataType. - * @param settings the new default settings. - */ - public void setDefaultSettings(Settings settings); - /** * Sets the comment for the component. * @param comment this components comment or null to clear comment. diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java index 456723fb78..afdc2ee086 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeComponentImpl.java @@ -33,7 +33,7 @@ public class DataTypeComponentImpl implements InternalDataTypeComponent, Seriali private CompositeDataTypeImpl parent; // parent prototype containing us private int offset; // offset in parent private int ordinal; // position in parent - private Settings settings; + private SettingsImpl defaultSettings; private String fieldName; // name of this prototype in the component private String comment; // comment about this component. @@ -219,7 +219,7 @@ public class DataTypeComponentImpl implements InternalDataTypeComponent, Seriali /** * Set the component ordinal of this component within its parent * data type. - * @param ordinal + * @param ordinal component ordinal */ void setOrdinal(int ordinal) { this.ordinal = ordinal; @@ -227,15 +227,18 @@ public class DataTypeComponentImpl implements InternalDataTypeComponent, Seriali @Override public Settings getDefaultSettings() { - if (settings == null) { - settings = new SettingsImpl(); + if (defaultSettings == null) { + DataTypeManager dataMgr = parent.getDataTypeManager(); + boolean immutableSettings = + dataMgr == null || !dataMgr.allowsDefaultComponentSettings(); + defaultSettings = new SettingsImpl(immutableSettings); + defaultSettings.setDefaultSettings(getDataType().getDefaultSettings()); } - return settings; + return defaultSettings; } - @Override - public void setDefaultSettings(Settings settings) { - this.settings = settings; + void invalidateSettings() { + defaultSettings = null; } @Override @@ -310,7 +313,7 @@ public class DataTypeComponentImpl implements InternalDataTypeComponent, Seriali @Override public void setDataType(DataType dt) { - // intended for internal use only + // intended for internal use only - note exsiting settings should be preserved dataType = dt; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeImpl.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeImpl.java index fc38b7c127..79137ab7a8 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeImpl.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeImpl.java @@ -24,11 +24,18 @@ import ghidra.util.*; /** * Base implementation for dataTypes. + * + * NOTE: Settings are immutable when a DataTypeManager has not been specified (i.e., null). */ public abstract class DataTypeImpl extends AbstractDataType { private final static SettingsDefinition[] EMPTY_DEFINITIONS = new SettingsDefinition[0]; + + // NOTE: Modification of default settings on Impl datatypes is generally blocked + // with the exception of TypeDef's and BuiltInDataType which have had a suitable + // defaultSettings implementation established by its DataTypeManager. protected Settings defaultSettings; + private List> parentList; private UniversalID universalID; private SourceArchive sourceArchive; @@ -43,7 +50,7 @@ public abstract class DataTypeImpl extends AbstractDataType { SourceArchive sourceArchive, long lastChangeTime, long lastChangeTimeInSourceArchive, DataTypeManager dataMgr) { super(path, name, dataMgr); - defaultSettings = new SettingsImpl(); + defaultSettings = SettingsImpl.NO_SETTINGS; parentList = new ArrayList<>(); this.universalID = universalID == null ? UniversalIdGenerator.nextID() : universalID; this.sourceArchive = sourceArchive; @@ -79,11 +86,6 @@ public abstract class DataTypeImpl extends AbstractDataType { } } - @Override - public void setDefaultSettings(Settings settings) { - defaultSettings = settings; - } - @Override public String getPathName() { return getDataTypePath().getPath(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManager.java index 129812659b..144eb8cfa9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeManager.java @@ -538,4 +538,18 @@ public interface DataTypeManager { * Use of {@link Set} implementations for containing DataTypes is also inefficient. */ public Set getDataTypesContaining(DataType dataType); + + /** + * Determine if settings are supported for BuiltIn datatypes within this + * datatype manager. + * @return true if BuiltIn Settings are permitted + */ + public boolean allowsDefaultBuiltInSettings(); + + /** + * Determine if settings are supported for datatype components within this + * datatype manager (i.e., for structure and union components). + * @return true if BuiltIn Settings are permitted + */ + public boolean allowsDefaultComponentSettings(); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeMnemonicSettingsDefinition.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeMnemonicSettingsDefinition.java index c722bc66c4..3f77587c19 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeMnemonicSettingsDefinition.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DataTypeMnemonicSettingsDefinition.java @@ -58,6 +58,11 @@ public class DataTypeMnemonicSettingsDefinition implements EnumSettingsDefinitio return style; } + @Override + public String getValueString(Settings settings) { + return choices[getChoice(settings)]; + } + @Override public int getChoice(Settings settings) { return getMnemonicStyle(settings); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DynamicDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DynamicDataType.java index 645c818dae..49fc1a41e5 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DynamicDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/DynamicDataType.java @@ -15,7 +15,6 @@ */ package ghidra.program.model.data; -import ghidra.docking.settings.SettingsImpl; import ghidra.program.model.address.Address; import ghidra.program.model.mem.MemBuffer; import ghidra.util.datastruct.SoftCacheMap; @@ -42,7 +41,6 @@ public abstract class DynamicDataType extends BuiltIn implements Dynamic { protected DynamicDataType(CategoryPath path, String name, DataTypeManager dtm) { super(path, name, dtm); this.map = new SoftCacheMap<>(100); - defaultSettings = new SettingsImpl(); } @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/EndianSettingsDefinition.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/EndianSettingsDefinition.java index 21e9da9413..c655139e4a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/EndianSettingsDefinition.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/EndianSettingsDefinition.java @@ -88,6 +88,11 @@ public class EndianSettingsDefinition implements EnumSettingsDefinition { return val; } + @Override + public String getValueString(Settings settings) { + return choices[getChoice(settings)]; + } + @Override public void setChoice(Settings settings, int value) { settings.setLong(ENDIAN_SETTING_NAME, value); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/GenericDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/GenericDataType.java index f751925721..12487f3fd4 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/GenericDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/GenericDataType.java @@ -75,5 +75,4 @@ public abstract class GenericDataType extends DataTypeImpl { } categoryPath = path; } - } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/IBO32DataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/IBO32DataType.java new file mode 100644 index 0000000000..517b41e39a --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/IBO32DataType.java @@ -0,0 +1,91 @@ +/* ### + * 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.program.model.data; + +/** + * IBO32DataType provides a Pointer-Typedef BuiltIn for + * a 32-bit Image Base Offset Relative Pointer. + */ +public class IBO32DataType extends AbstractPointerTypedefDataType { + + static final String NAME = "ibo32"; + +// TODO: remove old ImageBaseOffset32DataType implementation and uncomment +// static { +// ClassTranslator.put("ghidra.program.model.data.ImageBaseOffset32", +// IBO32DataType.class.getName()); +// ClassTranslator.put("ghidra.program.model.data.ImageBaseOffset32DataType", +// IBO32DataType.class.getName()); +// } + + /** + * Constructs a 32-bit Image Base Offset relative pointer-typedef. + */ + public IBO32DataType() { + this(DataType.DEFAULT, null); + } + + /** + * Constructs a 32-bit Image Base Offset relative pointer-typedef. + * @param dtm data-type manager whose data organization should be used + */ + public IBO32DataType(DataTypeManager dtm) { + this(DataType.DEFAULT, dtm); + } + + /** + * Constructs a 32-bit Image Base Offset relative pointer-typedef. + * @param referencedDataType data type this pointer-typedef points to + */ + public IBO32DataType(DataType referencedDataType) { + this(referencedDataType, null); + } + + /** + * Constructs a 32-bit Image Base Offset relative pointer-typedef. + * @param referencedDataType data type this pointer-typedef points to + * @param dtm data-type manager whose data organization should be used + */ + public IBO32DataType(DataType referencedDataType, DataTypeManager dtm) { + super(null, referencedDataType, 4, dtm); + PointerTypeSettingsDefinition.DEF.setType(getDefaultSettings(), PointerType.IBO); + } + + @Override + public String getDescription() { + return "32-bit Image Base Offset Relative Pointer-Typedef"; + } + + @Override + public DataType clone(DataTypeManager dtm) { + if (dataMgr == dtm) { + return this; + } + IBO32DataType td = new IBO32DataType(getReferencedDataType(), dtm); + TypedefDataType.copyTypeDefSettings(this, td, false); + return td; + } + + @Override + public String getName() { + DataType dt = getReferencedDataType(); + if (dt == null || Undefined.isUndefined(dt) || (dt instanceof VoidDataType)) { + return NAME; // use simple ibo name + } + return super.getName(); // use generated named + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/IBO64DataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/IBO64DataType.java new file mode 100644 index 0000000000..cc78d5641d --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/IBO64DataType.java @@ -0,0 +1,91 @@ +/* ### + * 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.program.model.data; + +/** + * IBO64DataType provides a Pointer-Typedef BuiltIn for + * a 64-bit Image Base Offset Relative Pointer. + */ +public class IBO64DataType extends AbstractPointerTypedefDataType { + + static final String NAME = "ibo64"; + +// TODO: remove old ImageBaseOffset64DataType implementation and uncomment +// static { +// ClassTranslator.put("ghidra.program.model.data.ImageBaseOffset64", +// IBO64DataType.class.getName()); +// ClassTranslator.put("ghidra.program.model.data.ImageBaseOffset64DataType", +// IBO64DataType.class.getName()); +// } + + /** + * Constructs a 64-bit Image Base Offset relative pointer-typedef. + */ + public IBO64DataType() { + this(DataType.DEFAULT, null); + } + + /** + * Constructs a 64-bit Image Base Offset relative pointer-typedef. + * @param dtm data-type manager whose data organization should be used + */ + public IBO64DataType(DataTypeManager dtm) { + this(DataType.DEFAULT, dtm); + } + + /** + * Constructs a 64-bit Image Base Offset relative pointer-typedef. + * @param referencedDataType data type this pointer-typedef points to + */ + public IBO64DataType(DataType referencedDataType) { + this(referencedDataType, null); + } + + /** + * Constructs a 64-bit Image Base Offset relative pointer-typedef. + * @param referencedDataType data type this pointer-typedef points to + * @param dtm data-type manager whose data organization should be used + */ + public IBO64DataType(DataType referencedDataType, DataTypeManager dtm) { + super(null, referencedDataType, 8, dtm); + PointerTypeSettingsDefinition.DEF.setType(getDefaultSettings(), PointerType.IBO); + } + + @Override + public String getDescription() { + return "64-bit Image Base Offset Relative Pointer-Typedef"; + } + + @Override + public DataType clone(DataTypeManager dtm) { + if (dataMgr == dtm) { + return this; + } + IBO64DataType td = new IBO64DataType(getReferencedDataType(), dtm); + TypedefDataType.copyTypeDefSettings(this, td, false); + return td; + } + + @Override + public String getName() { + DataType dt = getReferencedDataType(); + if (dt == null || Undefined.isUndefined(dt) || (dt instanceof VoidDataType)) { + return NAME; // use simple ibo name + } + return super.getName(); // use generated named + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MissingBuiltInDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MissingBuiltInDataType.java index 946ca9e224..7ea5637883 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MissingBuiltInDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MissingBuiltInDataType.java @@ -29,8 +29,6 @@ import ghidra.util.exception.DuplicateNameException; */ public class MissingBuiltInDataType extends DataTypeImpl implements Dynamic { - private final static long serialVersionUID = 1; - private String missingBuiltInClassPath; private String missingBuiltInName; @@ -205,4 +203,9 @@ public class MissingBuiltInDataType extends DataTypeImpl implements Dynamic { public DataType getReplacementBaseType() { return null; } + + @Override + public void setDefaultSettings(Settings settings) { + // ignore + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MutabilitySettingsDefinition.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MutabilitySettingsDefinition.java index bedd85445a..7ab887e13d 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MutabilitySettingsDefinition.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/MutabilitySettingsDefinition.java @@ -62,6 +62,11 @@ public class MutabilitySettingsDefinition implements EnumSettingsDefinition { return getMutabilityMode(settings); } + @Override + public String getValueString(Settings settings) { + return choices[getChoice(settings)]; + } + @Override public void setChoice(Settings settings, int value) { if (value < 0 || value > CONSTANT) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/OffsetMaskSettingsDefinition.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/OffsetMaskSettingsDefinition.java new file mode 100644 index 0000000000..e7d9ffcccd --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/OffsetMaskSettingsDefinition.java @@ -0,0 +1,128 @@ +/* ### + * 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.program.model.data; + +import java.math.BigInteger; + +import ghidra.docking.settings.NumberSettingsDefinition; +import ghidra.docking.settings.Settings; + +/** + * Setting definition for a pointer offset bit-mask to be applied prior to any + * bit-shift (if specified) during the computation of an actual address offset. + * Mask is defined as an unsigned long value where + * a value of zero (0) is ignored and has no affect on pointer computation. + */ +public class OffsetMaskSettingsDefinition + implements NumberSettingsDefinition, TypeDefSettingsDefinition { + + private static final String OFFSET_MASK_SETTING_NAME = "offset_mask"; + private static final String DESCRIPTION = + "Identifies bit-mask to be applied to a stored pointer offset prior to any shift"; + private static final String DISPLAY_NAME = "Offset Mask"; + private static BigInteger MAX_VALUE = new BigInteger("0ffffffffffffffff", 16); + + private static final long DEFAULT = -1; // unsigned mask - all bits are ones + + public static final OffsetMaskSettingsDefinition DEF = + new OffsetMaskSettingsDefinition(); + + private OffsetMaskSettingsDefinition() { + } + + @Override + public BigInteger getMaxValue() { + return MAX_VALUE; + } + + @Override + public boolean allowNegativeValue() { + return false; + } + + @Override + public boolean isHexModePreferred() { + return true; + } + + @Override + public long getValue(Settings settings) { + if (settings == null) { + return DEFAULT; + } + Long value = settings.getLong(OFFSET_MASK_SETTING_NAME); + if (value == null) { + return DEFAULT; + } + return value; + } + + @Override + public void setValue(Settings settings, long value) { + if (value == 0 || value == DEFAULT) { + settings.clearSetting(OFFSET_MASK_SETTING_NAME); + } + else { + settings.setLong(OFFSET_MASK_SETTING_NAME, value); + } + } + + @Override + public boolean hasValue(Settings settings) { + return getValue(settings) != DEFAULT; + } + + @Override + public String getName() { + return DISPLAY_NAME; + } + + @Override + public String getStorageKey() { + return OFFSET_MASK_SETTING_NAME; + } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public void clear(Settings settings) { + settings.clearSetting(OFFSET_MASK_SETTING_NAME); + } + + @Override + public void copySetting(Settings srcSettings, Settings destSettings) { + Long value = srcSettings.getLong(OFFSET_MASK_SETTING_NAME); + if (value == null) { + destSettings.clearSetting(OFFSET_MASK_SETTING_NAME); + } + else { + destSettings.setLong(OFFSET_MASK_SETTING_NAME, value); + } + } + + @Override + public String getAttributeSpecification(Settings settings) { + if (hasValue(settings)) { + long mask = getValue(settings); + return "mask(0x" + Long.toHexString(mask) + ")"; + } + return null; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/OffsetShiftSettingsDefinition.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/OffsetShiftSettingsDefinition.java new file mode 100644 index 0000000000..23acc392d8 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/OffsetShiftSettingsDefinition.java @@ -0,0 +1,122 @@ +/* ### + * 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.program.model.data; + +import java.math.BigInteger; + +import ghidra.docking.settings.NumberSettingsDefinition; +import ghidra.docking.settings.Settings; + +public class OffsetShiftSettingsDefinition + implements NumberSettingsDefinition, TypeDefSettingsDefinition { + + private static final String OFFSET_SHIFT_SETTING_NAME = "offset_shift"; + private static final String DESCRIPTION = + "Identifies bit-shift to be applied to a stored pointer offset (+left/-right)"; + private static final String DISPLAY_NAME = "Offset Shift"; + private static BigInteger MAX_VALUE = BigInteger.valueOf(64); + + private static final int DEFAULT = 0; + + public static final OffsetShiftSettingsDefinition DEF = + new OffsetShiftSettingsDefinition(); + + private OffsetShiftSettingsDefinition() { + } + + @Override + public BigInteger getMaxValue() { + return MAX_VALUE; + } + + @Override + public boolean allowNegativeValue() { + return true; + } + + @Override + public boolean isHexModePreferred() { + return false; + } + + @Override + public long getValue(Settings settings) { + if (settings == null) { + return DEFAULT; + } + Long value = settings.getLong(OFFSET_SHIFT_SETTING_NAME); + if (value == null) { + return DEFAULT; + } + return value; + } + + @Override + public void setValue(Settings settings, long value) { + if (value == DEFAULT) { + settings.clearSetting(OFFSET_SHIFT_SETTING_NAME); + } + else { + settings.setLong(OFFSET_SHIFT_SETTING_NAME, value); + } + } + + @Override + public boolean hasValue(Settings settings) { + return getValue(settings) != DEFAULT; + } + + @Override + public String getName() { + return DISPLAY_NAME; + } + + @Override + public String getStorageKey() { + return OFFSET_SHIFT_SETTING_NAME; + } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public void clear(Settings settings) { + settings.clearSetting(OFFSET_SHIFT_SETTING_NAME); + } + + @Override + public void copySetting(Settings srcSettings, Settings destSettings) { + Long value = srcSettings.getLong(OFFSET_SHIFT_SETTING_NAME); + if (value == null) { + destSettings.clearSetting(OFFSET_SHIFT_SETTING_NAME); + } + else { + destSettings.setLong(OFFSET_SHIFT_SETTING_NAME, value); + } + } + + @Override + public String getAttributeSpecification(Settings settings) { + if (hasValue(settings)) { + long shift = getValue(settings); + return "shift(" + Long.toString(shift) + ")"; + } + return null; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PaddingSettingsDefinition.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PaddingSettingsDefinition.java index 087abd88a1..d37bf58d24 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PaddingSettingsDefinition.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PaddingSettingsDefinition.java @@ -66,6 +66,11 @@ public class PaddingSettingsDefinition implements EnumSettingsDefinition { return UNPADDED_VALUE; } + @Override + public String getValueString(Settings settings) { + return choices[getChoice(settings)]; + } + @Override public void setChoice(Settings settings, int value) { settings.setLong(PADDED, value); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Pointer.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Pointer.java index 7fd9e5e281..2c93f306b1 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Pointer.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Pointer.java @@ -15,14 +15,21 @@ */ package ghidra.program.model.data; +import ghidra.program.database.data.PointerTypedefInspector; + /** * Interface for pointers */ public interface Pointer extends DataType { + /** + * Pointer representation used when unable to generate a suitable address + */ + public final String NaP = "NaP"; + /** * Returns the "pointed to" dataType - * + * @return referenced datatype (may be null) */ DataType getDataType(); @@ -33,4 +40,18 @@ public interface Pointer extends DataType { */ Pointer newPointer(DataType dataType); + /** + * Construct a pointer-typedef builder base on this pointer. + *
    + * Other construction options are provided when directly instantiating + * a {@link PointerTypedefBuilder}. In addition the utility class {@link PointerTypedefInspector} + * can be used to easily determine pointer-typedef settings. + * @return pointer-typedef builder + * @throws IllegalArgumentException if an invalid name is + * specified or pointer does not have a datatype manager. + */ + default PointerTypedefBuilder typedefBuilder() { + return new PointerTypedefBuilder(this, this.getDataTypeManager()); + } + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerDataType.java index 84e8c7512d..8d2810c556 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerDataType.java @@ -15,8 +15,7 @@ */ package ghidra.program.model.data; -import java.util.HashSet; -import java.util.Set; +import java.util.*; import ghidra.docking.settings.Settings; import ghidra.program.database.data.DataTypeUtilities; @@ -39,6 +38,12 @@ public class PointerDataType extends BuiltIn implements Pointer { public static final String POINTER_LABEL_PREFIX = "PTR"; public static final String POINTER_LOOP_LABEL_PREFIX = "PTR_LOOP"; + // NOTE: order dictates auto-name attribute ordering (order should not be changed) + private static TypeDefSettingsDefinition[] POINTER_TYPEDEF_SETTINGS_DEFS = + { PointerTypeSettingsDefinition.DEF, AddressSpaceSettingsDefinition.DEF, + OffsetMaskSettingsDefinition.DEF, OffsetShiftSettingsDefinition.DEF, + ComponentOffsetSettingsDefinition.DEF }; + protected DataType referencedDataType; protected int length; @@ -325,10 +330,7 @@ public class PointerDataType extends BuiltIn implements Pointer { @Override public Object getValue(MemBuffer buf, Settings settings, int len) { - - // TODO: Which address space should pointer refer to ?? - - return getAddressValue(buf, getLength(), buf.getAddress().getAddressSpace()); + return getAddressValue(buf, getLength(), settings); } @Override @@ -336,14 +338,125 @@ public class PointerDataType extends BuiltIn implements Pointer { return Address.class; } + @Override + public TypeDefSettingsDefinition[] getTypeDefSettingsDefinitions() { + return POINTER_TYPEDEF_SETTINGS_DEFS; + } + + /** + * Generate an address value based upon bytes stored at the specified buf + * location. The following settings, if specified, may influence the generated address: + *

      + *
    • {@link AddressSpaceSettingsDefinition}
    • + *
    • {@link OffsetShiftSettingsDefinition}
    • + *
    + * The default address space will be the same as the buffer's associated memory address. + * Interpretation of settings may depend on access to a {@link Memory} object associated + * with the specified {@link MemBuffer} buf. + * + * @param buf memory buffer positioned to stored pointer + * @param size pointer size in bytes + * @param settings settings which may influence address generation + * @return address value or null if unusable buf or data + */ + public static Address getAddressValue(MemBuffer buf, int size, Settings settings) { + + AddressSpace targetSpace = null; + + Memory mem = buf.getMemory(); + if (mem != null) { + Program program = mem.getProgram(); + String spaceName = AddressSpaceSettingsDefinition.DEF.getValue(settings); + if (spaceName != null) { + targetSpace = program.getAddressFactory().getAddressSpace(spaceName); + } + } + if (targetSpace == null) { + // default to address space associated with mem buffer + targetSpace = buf.getAddress().getAddressSpace(); + } + + if (targetSpace instanceof SegmentedAddressSpace) { + // ignore other settings + return getAddressValue(buf, size, targetSpace); + } + + Long offset = getStoredOffset(buf, size); + if (offset == null) { + return null; + } + + long addrOffset = offset; + + long mask = OffsetMaskSettingsDefinition.DEF.getValue(settings); + if (mask != 0) { + addrOffset &= mask; + } + + int shift = (int) OffsetShiftSettingsDefinition.DEF.getValue(settings); + if (shift < 0) { + addrOffset >>= -shift; + } + else { + addrOffset <<= shift; + } + + try { + PointerType choice = PointerTypeSettingsDefinition.DEF.getType(settings); + if (choice == PointerType.IBO && mem != null) { + // must ignore AddressSpaceSettingsDefinition + Address imageBase = mem.getProgram().getImageBase(); + targetSpace = imageBase.getAddressSpace(); + return imageBase.add(addrOffset * targetSpace.getAddressableUnitSize()); + } + else if (choice == PointerType.RELATIVE) { + // must ignore AddressSpaceSettingsDefinition + Address base = buf.getAddress(); + targetSpace = base.getAddressSpace(); + return base.add(addrOffset * targetSpace.getAddressableUnitSize()); + } + if (choice == PointerType.FILE_OFFSET) { + if (mem != null) { + List
    addressList = mem.locateAddressesForFileOffset(addrOffset); + if (addressList.size() == 1) { + return addressList.get(0); + } + } + return null; + } + + return targetSpace.getAddress(addrOffset, true); + } + catch (AddressOutOfBoundsException e) { + // offset too large + } + return null; + } + + /** + * Get signed offset value based upon bytes stored at the specified buf + * location + * + * @param buf memory buffer and stored pointer location + * @param size store pointer size in bytes + * @return stored offset value or null if unusable buf or data + */ + private static Long getStoredOffset(MemBuffer buf, int size) { + byte[] bytes = new byte[size]; + if (buf.getBytes(bytes, 0) != size) { + return null; + } + return DataConverter.getInstance(buf.isBigEndian()).getSignedValue(bytes, size); + } + /** * Generate an address value based upon bytes stored at the specified buf * location * * @param buf memory buffer and stored pointer location * @param size pointer size in bytes - * @param targetSpace address space for returned pointer - * @return pointer value or null if unusable buf or data + * @param targetSpace address space for returned address + * @return address value or null if unusable buf or data */ public static Address getAddressValue(MemBuffer buf, int size, AddressSpace targetSpace) { @@ -351,7 +464,7 @@ public class PointerDataType extends BuiltIn implements Pointer { return null; } - if (buf.getAddress() instanceof SegmentedAddress) { + if (targetSpace instanceof SegmentedAddressSpace) { try { // NOTE: conversion assumes a little-endian space return getSegmentedAddressValue(buf, size); @@ -369,18 +482,14 @@ public class PointerDataType extends BuiltIn implements Pointer { return null; } - byte[] bytes = new byte[size]; - if (buf.getBytes(bytes, 0) != size) { - return null; - } - - long val = DataConverter.getInstance(buf.isBigEndian()).getValue(bytes, size); - - try { - return targetSpace.getAddress(val, true); - } - catch (AddressOutOfBoundsException e) { - // offset too large + Long offset = getStoredOffset(buf, size); + if (offset != null) { + try { + return targetSpace.getAddress(offset, true); + } + catch (AddressOutOfBoundsException e) { + // offset too large + } } return null; } @@ -609,4 +718,5 @@ public class PointerDataType extends BuiltIn implements Pointer { public String toString() { return getName(); // always include pointer length } + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerType.java new file mode 100644 index 0000000000..b56932da4e --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerType.java @@ -0,0 +1,69 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.program.model.data; + +import java.util.NoSuchElementException; + +import ghidra.program.database.data.PointerTypedefInspector; + +/** + * PointerType specified the pointer-type associated with a pointer-typedef. + * @see PointerTypeSettingsDefinition + * @see PointerTypedefBuilder + * @see PointerTypedefInspector + */ +public enum PointerType { + /** + * Normal absolute pointer offset + */ + DEFAULT(0), + /** + * Pointer offset relative to program image base. + */ + IBO(1), + /** + * Pointer offset relative to pointer storage address. + * NOTE: This type has limited usefulness since it can only be applied to + * a pointer stored in memory based upon its storage location. Type-propogation + * should be avoided on the resulting pointer typedef. + */ + RELATIVE(2), + /** + * Pointer offset corresponds to file offset within an associated file. + */ + FILE_OFFSET(3); + + final int value; + + PointerType(int value) { + this.value = value; + } + + /** + * Get the type associated with the specified value. + * @param val type value + * @return type + * @throws NoSuchElementException if invalid value specified + */ + public static PointerType valueOf(int val) throws NoSuchElementException { + for (PointerType t : values()) { + if (t.value == val) { + return t; + } + } + throw new NoSuchElementException("unknown type value: " + val); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerTypeSettingsDefinition.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerTypeSettingsDefinition.java new file mode 100644 index 0000000000..c47a5a2700 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerTypeSettingsDefinition.java @@ -0,0 +1,166 @@ +/* ### + * 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.program.model.data; + +import java.util.NoSuchElementException; + +import ghidra.docking.settings.EnumSettingsDefinition; +import ghidra.docking.settings.Settings; + +/** + * The settings definition for the numeric display format + */ +public class PointerTypeSettingsDefinition + implements EnumSettingsDefinition, TypeDefSettingsDefinition { + + private static final String POINTER_TYPE_SETTINGS_NAME = "ptr_type"; + private static final String DESCRIPTION = + "Specifies the pointer type which affects interpretation of offset"; + private static final String DISPLAY_NAME = "Pointer Type"; + + private static final String[] choices = + { "default", "image-base-relative", "relative", "file-offset" }; + + public static final PointerTypeSettingsDefinition DEF = new PointerTypeSettingsDefinition(); // Format with HEX default + + private PointerTypeSettingsDefinition() { + } + + /** + * Returns the format based on the specified settings + * @param settings the instance settings or null for default value. + * @return the format value (HEX, DECIMAL, BINARY, OCTAL, CHAR) + */ + public PointerType getType(Settings settings) { + if (settings == null) { + return PointerType.DEFAULT; + } + Long value = settings.getLong(POINTER_TYPE_SETTINGS_NAME); + if (value == null) { + return PointerType.DEFAULT; + } + int type = (int) value.longValue(); + try { + return PointerType.valueOf(type); + } + catch (NoSuchElementException e) { + return PointerType.DEFAULT; + } + } + + @Override + public int getChoice(Settings settings) { + return getType(settings).value; + } + + @Override + public String getValueString(Settings settings) { + return choices[getChoice(settings)]; + } + + @Override + public void setChoice(Settings settings, int value) { + try { + setType(settings, PointerType.valueOf(value)); + } + catch (NoSuchElementException e) { + settings.clearSetting(POINTER_TYPE_SETTINGS_NAME); + } + } + + public void setType(Settings settings, PointerType type) { + if (type == PointerType.DEFAULT) { + settings.clearSetting(POINTER_TYPE_SETTINGS_NAME); + } + else { + settings.setLong(POINTER_TYPE_SETTINGS_NAME, type.value); + } + } + + @Override + public String[] getDisplayChoices(Settings settings) { + return choices; + } + + @Override + public String getName() { + return DISPLAY_NAME; + } + + @Override + public String getStorageKey() { + return POINTER_TYPE_SETTINGS_NAME; + } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public String getDisplayChoice(int value, Settings s1) { + return choices[value]; + } + + @Override + public void clear(Settings settings) { + settings.clearSetting(POINTER_TYPE_SETTINGS_NAME); + } + + @Override + public void copySetting(Settings settings, Settings destSettings) { + Long l = settings.getLong(POINTER_TYPE_SETTINGS_NAME); + if (l == null) { + destSettings.clearSetting(POINTER_TYPE_SETTINGS_NAME); + } + else { + destSettings.setLong(POINTER_TYPE_SETTINGS_NAME, l); + } + } + + @Override + public boolean hasValue(Settings setting) { + return setting.getValue(POINTER_TYPE_SETTINGS_NAME) != null; + } + + public String getDisplayChoice(Settings settings) { + return choices[getChoice(settings)]; + } + + /** + * Sets the settings object to the enum value indicating the specified choice as a string. + * @param settings the settings to store the value. + * @param choice enum string representing a choice in the enum. + */ + public void setDisplayChoice(Settings settings, String choice) { + for (int i = 0; i < choices.length; i++) { + if (choices[i].equals(choice)) { + setChoice(settings, i); + break; + } + } + } + + @Override + public String getAttributeSpecification(Settings settings) { + int choice = getChoice(settings); + if (choice != 0) { + return choices[choice]; + } + return null; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerTypedef.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerTypedef.java new file mode 100644 index 0000000000..7ecc1596e7 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerTypedef.java @@ -0,0 +1,104 @@ +/* ### + * 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.program.model.data; + +import java.util.Objects; + +import ghidra.program.model.address.AddressSpace; + +/** + * PointerTypedef provides a Pointer-Typedef template datatype + * which may be used as an alternative to {@link PointerTypedefBuilder} for + * select use cases. Once resolved this datatype is transformed into a + * standard {@link TypeDef} with appropropriate settings (see + * {@link TypeDefSettingsDefinition}). + *
    + * NOTE: The name of this class intentionally does not end with DataType + * since it does not implement a default constructor so it may not be treated + * like other {@link BuiltIn} datatypes which are managed by the + * <{@link BuiltInDataTypeManager}. + */ +public class PointerTypedef extends AbstractPointerTypedefDataType { + + /** + * Constructs a pointer-typedef which dereferences into a specific address space. + * @param typeDefName name of this pointer-typedef or null to force name generation. + * @param referencedDataType data type this pointer-typedef points to + * @param pointerSize pointer size in bytes or -1 for default pointer size based upon datatype manager + * @param dtm data-type manager whose data organization should be used (highly recommended, may be null) + * @param space address space to be used when dereferencing pointer offset + */ + public PointerTypedef(String typeDefName, DataType referencedDataType, int pointerSize, + DataTypeManager dtm, AddressSpace space) { + super(typeDefName, referencedDataType, pointerSize, dtm); + Objects.requireNonNull(space, "Address space must be specified"); + AddressSpaceSettingsDefinition.DEF.setValue(getDefaultSettings(), space.getName()); + } + + /** + * Constructs a pointer-typedef of a specific type + * @param typeDefName name of this pointer-typedef or null to force name generation. + * @param referencedDataType data type this pointer-typedef points to + * @param pointerSize pointer size in bytes or -1 for default pointer size based upon datatype manager + * @param dtm data-type manager whose data organization should be used (highly recommended, may be null) + * @param type pointer type (IBO, RELATIVE, FILE_OFFSET) + */ + public PointerTypedef(String typeDefName, DataType referencedDataType, int pointerSize, + DataTypeManager dtm, PointerType type) { + super(typeDefName, referencedDataType, pointerSize, dtm); + Objects.requireNonNull(type, "Pointer type required"); + PointerTypeSettingsDefinition.DEF.setType(getDefaultSettings(), type); + } + + /** + * Constructs a pointer-typedef without any settings + * @param typeDefName name of this pointer-typedef or null to force name generation. + * @param referencedDataType data type this pointer-typedef points to + * @param pointerSize pointer size in bytes or -1 for default pointer size based upon datatype manager + * @param dtm data-type manager whose data organization should be used (highly recommended, may be null) + */ + /* package */ PointerTypedef(String typeDefName, DataType referencedDataType, int pointerSize, + DataTypeManager dtm) { + super(typeDefName, referencedDataType, pointerSize, dtm); + } + + /** + * Constructs a pointer-typedef without any settings + * @param typeDefName name of this pointer-typedef or null to force name generation. + * @param pointerDataType associated pointer datatype + * @param dtm data-type manager whose data organization should be used (highly recommended, may be null) + */ + /* package */ PointerTypedef(String typeDefName, Pointer pointerDataType, DataTypeManager dtm) { + super(typeDefName, pointerDataType, dtm); + } + + @Override + public String getDescription() { + return "Pointer-Typedef"; + } + + @Override + public DataType clone(DataTypeManager dtm) { + if (dataMgr == dtm) { + return this; + } + Pointer ptrType = (Pointer) getDataType(); + String n = hasGeneratedNamed() ? null : getName(); + PointerTypedef td = new PointerTypedef(n, ptrType, getDataTypeManager()); + TypedefDataType.copyTypeDefSettings(this, td, false); + return td; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerTypedefBuilder.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerTypedefBuilder.java new file mode 100644 index 0000000000..0057a40d94 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/PointerTypedefBuilder.java @@ -0,0 +1,152 @@ +/* ### + * 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.program.model.data; + +import java.util.Objects; + +import ghidra.program.database.data.PointerTypedefInspector; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.symbol.OffsetReference; + +/** + * PointerTypedefBuilder provides a builder for creating {@link Pointer} - {@link TypeDef}s. + * These special typedefs allow a modified-pointer datatype to be used for special situations where + * a simple pointer will not suffice and special stored pointer interpretation/handling is required. + *
    + * This builder simplifies the specification of various {@link Pointer} modifiers during the + * construction of an associated {@link TypeDef}. + *
    + * A convenience method {@link Pointer#typedefBuilder()} also exists for creating a builder + * from a pointer instance. In addition the utility class {@link PointerTypedefInspector} + * can be used to easily determine pointer-typedef settings. + */ +public class PointerTypedefBuilder { + + private PointerTypedef typedef; + + /** + * Construct a {@link Pointer} - {@link TypeDef} builder. + * @param baseDataType baseDataType or null to use a default pointer + * @param pointerSize pointer size or -1 to use default pointer size for specified datatype manager. + * @param dtm datatype manager (highly recommended although may be null) + */ + public PointerTypedefBuilder(DataType baseDataType, int pointerSize, DataTypeManager dtm) { + typedef = new PointerTypedef(null, baseDataType, pointerSize, dtm); + } + + /** + * Construct a {@link Pointer} - {@link TypeDef} builder. + * @param pointerDataType base pointer datatype (required) + * @param dtm datatype manager (highly recommended although may be null) + */ + public PointerTypedefBuilder(Pointer pointerDataType, DataTypeManager dtm) { + Objects.requireNonNull(pointerDataType, "Pointer datatype required"); + typedef = new PointerTypedef(null, pointerDataType, dtm); + } + + /** + * Set pointer-typedef name. If not specified a default name will be generated based + * upon the associated pointer type and the specified settings. + * @param name typedef name + * @return this builder + */ + public PointerTypedefBuilder name(String name) { + typedef.setTypedefName(name); + return this; + } + + /** + * Update pointer type. + * @param type pointer type + * @return this builder + */ + public PointerTypedefBuilder type(PointerType type) { + PointerTypeSettingsDefinition.DEF.setType(typedef.getDefaultSettings(), type); + return this; + } + + /** + * Update pointer offset bit-shift when translating to an absolute memory offset. + * If specified, bit-shift will be applied after applying any specified bit-mask. + * @param shift bit-shift (right: positive, left: negative) + * @return this builder + */ + public PointerTypedefBuilder bitShift(int shift) { + OffsetShiftSettingsDefinition.DEF.setValue(typedef.getDefaultSettings(), shift); + return this; + } + + /** + * Update pointer offset bit-mask when translating to an absolute memory offset. + * If specified, bit-mask will be AND-ed with stored offset prior to any + * specified bit-shift. + * @param unsignedMask unsigned bit-mask + * @return this builder + */ + public PointerTypedefBuilder bitMask(long unsignedMask) { + OffsetMaskSettingsDefinition.DEF.setValue(typedef.getDefaultSettings(), unsignedMask); + return this; + } + + /** + * Update pointer relative component-offset. This setting is interpretted in two + * ways: + *
      + *
    • The specified offset is considered to be relative to the start of the base datatype + * (e.g., structure). It may refer to a component-offset within the base datatype or outside of + * it.
    • + *
    • When pointer-typedef is initially applied to memory, an {@link OffsetReference} will be produced + * by subtracting the component-offset from the stored pointer offset to determine the + * base-offset for the reference. While the xref will be to the actual referenced location, the + * reference markup will be shown as <base>+<offset>
    • + *
    + * @param offset component offset relative to a base-offset and associated base-datatype + * @return this builder + */ + public PointerTypedefBuilder componentOffset(long offset) { + ComponentOffsetSettingsDefinition.DEF.setValue(typedef.getDefaultSettings(), offset); + return this; + } + + /** + * Update pointer referenced address space when translating to an absolute memory offset. + * @param space pointer referenced address space or null for default space + * @return this builder + */ + public PointerTypedefBuilder addressSpace(AddressSpace space) { + AddressSpaceSettingsDefinition.DEF.setValue(typedef.getDefaultSettings(), + space != null ? space.getName() : null); + return this; + } + + /** + * Update pointer referenced address space when translating to an absolute memory offset. + * @param spaceName pointer referenced address space or null for default space + * @return this builder + */ + public PointerTypedefBuilder addressSpace(String spaceName) { + AddressSpaceSettingsDefinition.DEF.setValue(typedef.getDefaultSettings(), spaceName); + return this; + } + + /** + * Build pointer-typedef with specified settings. + * @return unresolved pointer typedef + */ + public TypeDef build() { + return typedef; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ProgramBasedDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ProgramBasedDataTypeManager.java index ff4633ecc9..c9ef0e0286 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ProgramBasedDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ProgramBasedDataTypeManager.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +15,133 @@ */ package ghidra.program.model.data; +import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; /** * Extends DataTypeManager to include methods specific to a data type manager for * a program. */ public interface ProgramBasedDataTypeManager extends DomainFileBasedDataTypeManager { + + /** + * Get the program instance associated with this datatype manager + * @return program instance associated with this datatype manager + */ Program getProgram(); + + /** + * Set the long value for instance settings. + * + * @param dataAddr min address of data + * @param name settings name + * @param value value of setting + * @return true if the settings actually changed + */ + public boolean setLongSettingsValue(Address dataAddr, String name, long value); + + /** + * Set the string value for instance settings. + * + * @param dataAddr min address of data + * @param name settings name + * @param value value of setting + * @return true if the settings actually changed + */ + public boolean setStringSettingsValue(Address dataAddr, String name, String value); + + /** + * Set the Object settings. + * + * @param dataAddr min address of data + * @param name the name of the settings + * @param value the value for the settings, must be either a String, byte[] + * or Long + * @return true if the settings were updated + */ + public boolean setSettings(Address dataAddr, String name, Object value); + + /** + * Get the long value for an instance setting. + * + * @param dataAddr min address of data + * @param name settings name + * @return null if the named setting was not found + */ + public Long getLongSettingsValue(Address dataAddr, String name); + + /** + * Get the String value for an instance setting. + * + * @param dataAddr min address of data + * @param name settings name + * @return null if the named setting was not found + */ + public String getStringSettingsValue(Address dataAddr, String name); + + /** + * Gets the value of a settings as an object (either String, byte[], or Long). + * + * @param dataAddr the address of the data for this settings + * @param name the name of settings. + * @return the settings object + */ + public Object getSettings(Address dataAddr, String name); + + /** + * Clear the setting + * + * @param dataAddr min address of data + * @param name settings name + * @return true if the settings were cleared + */ + public boolean clearSetting(Address dataAddr, String name); + + /** + * Clear all settings at the given address. + * + * @param dataAddr the address for this settings. + */ + public void clearAllSettings(Address dataAddr); + + /** + * Move the settings in the range to the new start address + * + * @param fromAddr start address from where to move + * @param toAddr new Address to move to + * @param length number of addresses to move + * @param monitor progress monitor + * @throws CancelledException if the operation was cancelled + */ + public void moveAddressRange(Address fromAddr, Address toAddr, long length, TaskMonitor monitor) + throws CancelledException; + + /** + * Returns all the instance Settings names used at the given address + * + * @param dataAddr the address + * @return the names + */ + public String[] getInstanceSettingsNames(Address dataAddr); + + /** + * Returns true if no settings are set for the given address + * + * @param dataAddr the address to test + * @return true if not settings + */ + public boolean isEmptySetting(Address dataAddr); + + /** + * Removes all settings in the range + * + * @param startAddr the first address in the range. + * @param endAddr the last address in the range. + * @param monitor the progress monitor + * @throws CancelledException if the user cancelled the operation. + */ + public void deleteAddressRange(Address startAddr, Address endAddr, TaskMonitor monitor) + throws CancelledException; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ReadOnlyDataTypeComponent.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ReadOnlyDataTypeComponent.java index d239ac0ae6..9c12719ad0 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ReadOnlyDataTypeComponent.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/ReadOnlyDataTypeComponent.java @@ -35,8 +35,8 @@ public class ReadOnlyDataTypeComponent implements DataTypeComponent, Serializabl private final int length; // my length private String fieldName; // name of this prototype in the component - private Settings settings; - + private Settings defaultSettings; + /** * Create a new DataTypeComponent * @param dataType the dataType for this component @@ -148,15 +148,12 @@ public class ReadOnlyDataTypeComponent implements DataTypeComponent, Serializabl @Override public Settings getDefaultSettings() { - if (settings == null) { - settings = new SettingsImpl(); + if (defaultSettings == null) { + SettingsImpl settings = new SettingsImpl(true); + settings.setDefaultSettings(dataType.getDefaultSettings()); + defaultSettings = settings; } - return settings; - } - - @Override - public void setDefaultSettings(Settings settings) { - this.settings = settings; + return defaultSettings; } @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java index 45209e1335..a708df4e27 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/StructureDataType.java @@ -565,7 +565,7 @@ public class StructureDataType extends CompositeDataTypeImpl implements Structur * @throws IllegalArgumentException if the specified data type is not allowed to be added to * this composite data type or an invalid length is specified. */ - private DataTypeComponent doAdd(DataType dataType, int length, String componentName, + private DataTypeComponentImpl doAdd(DataType dataType, int length, String componentName, String comment, boolean packAndNotify) throws IllegalArgumentException { @@ -1107,7 +1107,6 @@ public class StructureDataType extends CompositeDataTypeImpl implements Structur struct.setDescription(getDescription()); struct.replaceWith(this); return struct; - } @Override @@ -1169,7 +1168,6 @@ public class StructureDataType extends CompositeDataTypeImpl implements Structur notifySizeChanged(); } -// TODO: Rename private void doReplaceWithPacked(Structure struct) { // assumes components is clear and that alignment characteristics have been set DataTypeComponent[] otherComponents = struct.getDefinedComponents(); @@ -1180,7 +1178,6 @@ public class StructureDataType extends CompositeDataTypeImpl implements Structur } } -// TODO: Rename private void doReplaceWithNonPacked(Structure struct) throws IllegalArgumentException { // assumes components is clear and that alignment characteristics have been set. if (struct.isNotYetDefined()) { @@ -1319,6 +1316,7 @@ public class StructureDataType extends CompositeDataTypeImpl implements Structur comp.getDataType().removeParent(this); comp.setDataType(newDt); + comp.invalidateSettings(); newDt.addParent(this); if (isPackingEnabled()) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TerminatedSettingsDefinition.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TerminatedSettingsDefinition.java index 32c7b80d93..1228652316 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TerminatedSettingsDefinition.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TerminatedSettingsDefinition.java @@ -62,6 +62,11 @@ public class TerminatedSettingsDefinition implements EnumSettingsDefinition { return UNTERMINATED_VALUE; } + @Override + public String getValueString(Settings settings) { + return choices[getChoice(settings)]; + } + @Override public void setChoice(Settings settings, int value) { settings.setLong(TERMINATED, value); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TypeDef.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TypeDef.java index 846bac3737..cb9419a6a3 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TypeDef.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TypeDef.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +15,9 @@ */ package ghidra.program.model.data; +import ghidra.docking.settings.Settings; +import ghidra.docking.settings.SettingsDefinition; + /** * The typedef interface */ @@ -32,4 +34,48 @@ public interface TypeDef extends DataType { * chains of typedefs as necessary. */ public DataType getBaseDataType(); + + /** + * Determine if this is a Pointer-TypeDef + * @return true if base datatype is a pointer + */ + public default boolean isPointer() { + return (getBaseDataType() instanceof Pointer); + } + + /** + * Compare the settings of two datatypes which correspond to a + * {@link TypeDefSettingsDefinition}. + *

    + * NOTE: It is required that both datatypes present their settings + * definitions in the same order (see {@link DataType#getSettingsDefinitions}) + * to be considered the same. + * @param dt other typedef to compare with + * @return true if both datatypes have the same settings defined + * which correspond to {@link TypeDefSettingsDefinition} and have the + * same values, else false. + */ + public default boolean hasSameTypeDefSettings(TypeDef dt) { + SettingsDefinition[] defs1 = getSettingsDefinitions(); + SettingsDefinition[] defs2 = dt.getSettingsDefinitions(); + if (defs1.length != defs2.length) { + return false; + } + + Settings settings1 = getDefaultSettings(); + Settings settings2 = dt.getDefaultSettings(); + + for (int i = 0; i < defs1.length; i++) { + SettingsDefinition def = defs1[i]; + if (!defs2[i].getClass().equals(def.getClass())) { + return false; + } + if (def instanceof TypeDefSettingsDefinition) { + if (!def.hasSameValue(settings1, settings2)) { + return false; + } + } + } + return true; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TypeDefSettingsDefinition.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TypeDefSettingsDefinition.java new file mode 100644 index 0000000000..95f05bebe9 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TypeDefSettingsDefinition.java @@ -0,0 +1,51 @@ +/* ### + * 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.program.model.data; + +import ghidra.docking.settings.Settings; +import ghidra.docking.settings.SettingsDefinition; + +/** + * TypeDefSettingsDefinition specifies a {@link SettingsDefinition} whose + * use as a {@link TypeDef} setting will be available for use within a non-Program + * DataType archive. Such settings will be considered for DataType equivalence checks and + * preserved during DataType cloning and resolve processing. As such, these settings + * are only currently supported as a default-setting on a {@link TypeDef} + * (see {@link DataType#getDefaultSettings()}) and do not support component-specific + * or data-instance use. + * + * NOTE: Full support for this type of setting has only been fully implemented for TypeDef + * in support. There may be quite a few obstacles to overcome when introducing such + * settings to a different datatype. + */ +public interface TypeDefSettingsDefinition extends SettingsDefinition { + + /** + * Get the storage key which should be used when storing a key/value + * map entry which corresponds to this settings definition. + * @return settings storage key + */ + String getStorageKey(); + + /** + * Get the {@link TypeDef} attribute specification for this setting and its + * current value. + * @param settings typedef settings + * @return attribute specification or null if not currently set. + */ + String getAttributeSpecification(Settings settings); + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TypedefDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TypedefDataType.java index a7c3222a92..3a495f0550 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TypedefDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/TypedefDataType.java @@ -15,19 +15,21 @@ */ package ghidra.program.model.data; -import ghidra.docking.settings.Settings; -import ghidra.docking.settings.SettingsDefinition; +import ghidra.docking.settings.*; import ghidra.program.database.data.DataTypeUtilities; import ghidra.program.model.mem.MemBuffer; import ghidra.util.UniversalID; /** * - * Basic implementation for the typedef dataType + * Basic implementation for the typedef dataType. + * + * NOTE: Settings are immutable when a DataTypeManager has not been specified (i.e., null). */ public class TypedefDataType extends GenericDataType implements TypeDef { private DataType dataType; + private SettingsDefinition[] settingsDef; private boolean deleted = false; /** @@ -61,6 +63,7 @@ public class TypedefDataType extends GenericDataType implements TypeDef { validate(dt); this.dataType = dt.clone(dtm); dt.addParent(this); + defaultSettings = null; // use lazy initialization } /** @@ -83,6 +86,7 @@ public class TypedefDataType extends GenericDataType implements TypeDef { validate(dt); this.dataType = dt.clone(dtm); dt.addParent(this); + defaultSettings = null; // use lazy initialization } private void validate(DataType dt) { @@ -123,6 +127,9 @@ public class TypedefDataType extends GenericDataType implements TypeDef { if (!DataTypeUtilities.equalsIgnoreConflict(name, td.getName())) { return false; } + if (!hasSameTypeDefSettings(td)) { + return false; + } return DataTypeUtilities.isSameOrEquivalentDataType(getDataType(), td.getDataType()); } return false; @@ -169,17 +176,22 @@ public class TypedefDataType extends GenericDataType implements TypeDef { } @Override - public DataType clone(DataTypeManager dtm) { + public TypedefDataType clone(DataTypeManager dtm) { if (getDataTypeManager() == dtm) { return this; } - return new TypedefDataType(categoryPath, name, dataType, getUniversalID(), + TypedefDataType typeDef = + new TypedefDataType(categoryPath, name, dataType, getUniversalID(), getSourceArchive(), getLastChangeTime(), getLastChangeTimeInSourceArchive(), dtm); + copyTypeDefSettings(this, typeDef, false); + return typeDef; } @Override - public DataType copy(DataTypeManager dtm) { - return new TypedefDataType(categoryPath, name, dataType, dtm); + public TypedefDataType copy(DataTypeManager dtm) { + TypedefDataType typeDef = new TypedefDataType(categoryPath, name, dataType, dtm); + copyTypeDefSettings(this, typeDef, false); + return typeDef; } @Override @@ -219,13 +231,66 @@ public class TypedefDataType extends GenericDataType implements TypeDef { @Override public SettingsDefinition[] getSettingsDefinitions() { - return dataType.getSettingsDefinitions(); + if (settingsDef == null) { + DataType dt = getDataType(); + SettingsDefinition[] settingsDefinitions = dt.getSettingsDefinitions(); + TypeDefSettingsDefinition[] typeDefSettingsDefinitions = + dt.getTypeDefSettingsDefinitions(); + settingsDef = new SettingsDefinition[settingsDefinitions.length + + typeDefSettingsDefinitions.length]; + System.arraycopy(settingsDefinitions, 0, settingsDef, 0, settingsDefinitions.length); + System.arraycopy(typeDefSettingsDefinitions, 0, settingsDef, settingsDefinitions.length, + typeDefSettingsDefinitions.length); + } + return settingsDef; + } + + @Override + public TypeDefSettingsDefinition[] getTypeDefSettingsDefinitions() { + return getDataType().getTypeDefSettingsDefinitions(); + } + + @Override + public Settings getDefaultSettings() { + // This class allows for lazy initialization of defaultSettings + // Only applies if DataTypeManager has been specified + if (defaultSettings == null) { + DataType dt = getDataType(); + SettingsImpl settings; + if (dt.getTypeDefSettingsDefinitions().length == 0) { + // No viable settings - block changes to default settings + settings = new SettingsImpl(true); + } + else { + // Limit default settings changes to allowed definitions + // which support resolve and equivalence checks + settings = new SettingsImpl(n -> isAllowedSetting(n)); + } + settings.setDefaultSettings(getDataType().getDefaultSettings()); + defaultSettings = settings; + } + return defaultSettings; + } + + private boolean isAllowedSetting(String settingName) { + // non-TypeDefSettingsDefinition settings are not permitted in Impl TypeDef + // since they will be discarded during resolve and ignored for equivalence checks + for (TypeDefSettingsDefinition def : getTypeDefSettingsDefinitions()) { + if (def.getStorageKey().equals(settingName)) { + return true; + } + } + return false; } @Override public void dataTypeReplaced(DataType oldDt, DataType newDt) { validate(newDt); if (oldDt == dataType) { + settingsDef = null; + if (dataMgr != null) { + defaultSettings = null; + } dataType = newDt; oldDt.removeParent(this); newDt.addParent(this); @@ -254,4 +319,57 @@ public class TypedefDataType extends GenericDataType implements TypeDef { return "typedef " + getName() + " " + dataType.getName(); } + /** + * Copy all default settings , which correspond to a TypeDefSettingsDefinition, + * from the specified src TypeDef to the specified dest TypeDef. + * @param src settings source TypeDef + * @param dest settings destination TypeDef + * @param clearBeforeCopy if true dest default settings will be cleared before copy performed + */ + public static void copyTypeDefSettings(TypeDef src, TypeDef dest, boolean clearBeforeCopy) { + if (clearBeforeCopy) { + Settings settings = dest.getDefaultSettings(); + settings.clearAllSettings(); + } + + Settings otherSettings = src.getDefaultSettings(); + if (otherSettings.isEmpty()) { + return; + } + + Settings settings = dest.getDefaultSettings(); + for (TypeDefSettingsDefinition def : dest.getTypeDefSettingsDefinitions()) { + def.copySetting(otherSettings, settings); + } + } + + /** + * Generate a name for the typedef based upon its current {@link TypeDefSettingsDefinition} settings. + * @param modelType model typedef from which name should be derived + * @return generated typedef auto-name with attribute specification + */ + public static String generateTypedefName(TypeDef modelType) { + + // Examples: + // string *32 __attribute__((relative)) + // char *32 __attribute__((image-base-relative)) + // char *16 __attribute__((space(data))) + + Settings settings = modelType.getDefaultSettings(); + StringBuilder attributesBuf = new StringBuilder(); + for (TypeDefSettingsDefinition def : modelType.getTypeDefSettingsDefinitions()) { + String attribute = def.getAttributeSpecification(settings); + if (attribute != null) { + if (attributesBuf.length() != 0) { + attributesBuf.append(','); + } + attributesBuf.append(attribute); + } + } + StringBuilder buf = new StringBuilder(modelType.getDataType().getName()); + buf.append(" __attribute__(("); + buf.append(attributesBuf); + buf.append("))"); + return buf.toString(); + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/UnionDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/UnionDataType.java index ac836235ae..7ac6b3aa4f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/UnionDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/UnionDataType.java @@ -162,7 +162,7 @@ public class UnionDataType extends CompositeDataTypeImpl implements UnionInterna return length; } - DataTypeComponent doAdd(DataType dataType, int length, String componentName, String comment) + DataTypeComponentImpl doAdd(DataType dataType, int length, String componentName, String comment) throws IllegalArgumentException { dataType = validateDataType(dataType); @@ -549,6 +549,7 @@ public class UnionDataType extends CompositeDataTypeImpl implements UnionInterna oldDt.removeParent(this); dtc.setLength(len); dtc.setDataType(replacementDt); + dtc.invalidateSettings(); replacementDt.addParent(this); changed = true; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/LanguageVersionException.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/LanguageVersionException.java new file mode 100644 index 0000000000..87f27a43a8 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/LanguageVersionException.java @@ -0,0 +1,162 @@ +/* ### + * 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.program.model.lang; + +import ghidra.program.util.*; +import ghidra.util.Msg; +import ghidra.util.exception.VersionException; + +public class LanguageVersionException extends VersionException { + + private Language oldLanguage; + private LanguageTranslator languageTranslator; + + /** + * Construct a minor upgradeable language version exception + */ + public LanguageVersionException() { + super(true); + } + + /** + * Construct a major upgradeable language version exception + * @param oldLanguage old language stub + * @param languageTranslator language transalator + */ + public LanguageVersionException(Language oldLanguage, + LanguageTranslator languageTranslator) { + super(true); + this.oldLanguage = oldLanguage; + this.languageTranslator = languageTranslator; + } + + /** + * Old language stub if language translation required + * @return Old language stub or null + */ + public Language getOldLanguage() { + return oldLanguage; + } + + /** + * Old language upgrade translator if language translation required + * @return language upgrade translator or null + */ + public LanguageTranslator getLanguageTranslator() { + return languageTranslator; + } + + /** + * Check language against required version information. If not a match or upgradeable + * a {@link LanguageNotFoundException} will be thrown. If an upgradeable {@link LanguageVersionException} + * is returned, a major version change will also include the appropriate Old-Language stub and + * {@link LanguageTranslator} required to facilitate a language upgrade. + * @param language language corresponding to desired language ID + * @param languageVersion required major language version + * @param languageMinorVersion required minor language version. A negative minor version will be ignored. + * @return null if language matches, otherwise an upgradeable {@link LanguageVersionException}. + * @throws LanguageNotFoundException if language is a mismatch and is not upgradeable. + */ + public static LanguageVersionException check(Language language, int languageVersion, + int languageMinorVersion) throws LanguageNotFoundException { + + LanguageID languageID = language.getLanguageID(); + + if (language.getVersion() > languageVersion) { + + Language newLanguage = language; + + Language oldLanguage = OldLanguageFactory.getOldLanguageFactory() + .getOldLanguage(languageID, languageVersion); + if (oldLanguage == null) { + // Assume minor version behavior - old language does not exist for current major version + Msg.error(LanguageVersionException.class, "Old language specification not found: " + + languageID + " (Version " + languageVersion + ")"); + return new LanguageVersionException(); + } + + // Ensure that we can upgrade the language + LanguageTranslator languageUpgradeTranslator = + LanguageTranslatorFactory.getLanguageTranslatorFactory() + .getLanguageTranslator(oldLanguage, newLanguage); + if (languageUpgradeTranslator == null) { + +// TODO: This is a bad situation!! Most language revisions should be supportable, if not we have no choice but to throw +// a LanguageNotFoundException until we figure out how to deal with nasty translations which require +// a complete redisassembly and possibly auto analysis. + + throw new LanguageNotFoundException(language.getLanguageID(), + "(Ver " + languageVersion + "." + languageMinorVersion + " -> " + + newLanguage.getVersion() + "." + newLanguage.getMinorVersion() + + ") language version translation not supported"); + } + language = oldLanguage; + return new LanguageVersionException(oldLanguage, languageUpgradeTranslator); + } + else if (language.getVersion() == languageVersion && languageMinorVersion < 0) { + // Minor version ignored - considered as match if major number matches + return null; + } + else if (language.getVersion() == languageVersion && + language.getMinorVersion() > languageMinorVersion) { + // Minor version change - translator not needed (languageUpgradeTranslator is null) + return new LanguageVersionException(); + } + else if (language.getMinorVersion() != languageMinorVersion || + language.getVersion() != languageVersion) { + throw new LanguageNotFoundException(language.getLanguageID(), languageVersion, + languageMinorVersion); + } + return null; // language matches + } + + /** + * Determine if a missing language resulting in a {@link LanguageNotFoundException} can be + * upgraded to a replacement language via a language translation. + * @param e original {@link LanguageNotFoundException} + * @param languageID language ID of original language requested + * @param languageVersion original language major version + * @return upgradeable {@link LanguageVersionException} + * @throws LanguageNotFoundException original exception if a language transaltion is not available + */ + public static LanguageVersionException checkForLanguageChange(LanguageNotFoundException e, + LanguageID languageID, int languageVersion) throws LanguageNotFoundException { + + LanguageTranslator languageUpgradeTranslator = + LanguageTranslatorFactory.getLanguageTranslatorFactory() + .getLanguageTranslator(languageID, languageVersion); + if (languageUpgradeTranslator == null) { + throw e; + } + + Language oldLanguage = languageUpgradeTranslator.getOldLanguage(); + LanguageID oldLanguageID = oldLanguage.getLanguageID(); + + LanguageVersionException ve = + new LanguageVersionException(oldLanguage, languageUpgradeTranslator); + LanguageID newLangName = languageUpgradeTranslator.getNewLanguage().getLanguageID(); + String message; + if (oldLanguageID.equals(newLangName)) { + message = "Program requires a processor language version change"; + } + else { + message = "Program requires a processor language change to: " + newLangName; + } + ve.setDetailMessage(message); + return ve; + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/DataStub.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/DataStub.java index 3057bb9b34..0131532cdd 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/DataStub.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/DataStub.java @@ -332,11 +332,6 @@ public class DataStub implements Data { throw new UnsupportedOperationException(); } - @Override - public byte[] getByteArray(String name) { - throw new UnsupportedOperationException(); - } - @Override public Object getValue(String name) { throw new UnsupportedOperationException(); @@ -352,11 +347,6 @@ public class DataStub implements Data { throw new UnsupportedOperationException(); } - @Override - public void setByteArray(String name, byte[] value) { - throw new UnsupportedOperationException(); - } - @Override public void setValue(String name, Object value) { throw new UnsupportedOperationException(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java index 63b9b912db..ccc28d81fa 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Arrays; import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.program.database.data.PointerTypedefInspector; import ghidra.program.model.data.*; import ghidra.program.model.data.Enum; import ghidra.program.model.lang.CompilerSpec; @@ -250,6 +251,103 @@ public class PcodeDataTypeManager { } } + /** + * Get the inner data-type being referred to by an offset from a relative/shifted pointer. + * Generally we expect the base of the relative pointer to be a structure and the offset + * refers to a (possibly nested) field. In this case, we return the data-type of the field. + * Otherwise return an "undefined" data-type. + * @param base is the base data-type of the relative pointer + * @param offset is the offset into the base data-type + * @return the inner data-type + */ + private DataType findSubType(DataType base, int offset) { + if (base instanceof TypeDef) { + base = ((TypeDef) base).getBaseDataType(); + } + while (base instanceof Structure) { + DataTypeComponent component = ((Structure) base).getComponentContaining(offset); + if (component == null) { + break; + } + base = component.getDataType(); + offset -= component.getOffset(); + if (offset == 0) { + return base; + } + } + return Undefined1DataType.dataType; + } + + /** + * Build an XML representation of a pointer with an associated offset relative to a base data-type. + * The pointer is encoded as a TypeDef (of a Pointer). The "pointed to" object is the base data-type, + * the relative offset is passed in, and other properties come from the TypeDef. + * @param resBuf is the output buffer accumulating the XML + * @param type is the TypeDef encoding the relative pointer + * @param offset is the relative offset (already extracted from the TypeDef) + */ + private void buildPointerRelative(StringBuilder resBuf, TypeDef type, long offset) { + Pointer pointer = (Pointer) type.getBaseDataType(); + resBuf.append("\n"); + DataType parent = pointer.getDataType(); + DataType ptrto = findSubType(parent, (int) offset); + buildTypeRef(resBuf, ptrto, 1); + buildTypeRef(resBuf, parent, 1); + resBuf.append("\n").append(offset).append("\n"); + resBuf.append(""); + } + + /** + * Build an XML representation of a TypeDef data-type. Generally this sends + * a \ tag with a \ reference to the underlying data-type being typedefed, + * but we check for Settings on the TypeDef object that can indicate + * specialty data-types with their own XML format. + * @param resBuf is the output buffer accumulating the XML + * @param type is the TypeDef to build the XML for + * @param size is the size of the data-type for the specific instantiation + */ + private void buildTypeDef(StringBuilder resBuf, TypeDef type, int size) { + DataType refType = type.getDataType(); + int sz = refType.getLength(); + if (sz <= 0) { + sz = size; + } + + if (type.isPointer()) { + if (hasUnsupportedTypedefSettings(type)) { + // switch refType to undefined type if pointer-typedef settings are unsupported + refType = Undefined.getUndefinedDataType(sz); + } + else { + long offset = PointerTypedefInspector.getPointerComponentOffset(type); + if (offset != 0) { + buildPointerRelative(resBuf, type, offset); + return; + } + } + } + resBuf.append("'); + + buildTypeRef(resBuf, refType, sz); + resBuf.append(""); + return; + } + + private boolean hasUnsupportedTypedefSettings(TypeDef type) { + return (PointerTypedefInspector.getPointerType(type) != PointerType.DEFAULT || + PointerTypedefInspector.hasPointerBitShift(type) || + PointerTypedefInspector.hasPointerBitMask(type)); + } + /** * Generate an XML tag describing the given data-type. Most data-types produce a {@code } tag, * fully describing the data-type. Where possible a {@code } tag is produced, which just gives @@ -358,7 +456,25 @@ public class PcodeDataTypeManager { resBuf.append("\n"); } - private String buildTypeInternal(StringBuilder resBuf, DataType type, int size) { + /** + * Build an XML document string representing the type information for a data type + * + * @param resBuf is the stream to append the document to + * @param type data type to build XML for + * @param size size of the data type + */ + public void buildType(StringBuilder resBuf, DataType type, int size) { + if (type != null && type.getDataTypeManager() != progDataTypes) { + type = type.clone(progDataTypes); + } + if ((type instanceof VoidDataType) || (type == null)) { + resBuf.append(""); + return; + } + else if (type instanceof TypeDef) { + buildTypeDef(resBuf, (TypeDef) type, size); + return; + } resBuf.append("'); } resBuf.append(""); - return resBuf.toString(); } private void appendNameIdAttributes(StringBuilder resBuf, DataType type) { @@ -623,37 +738,6 @@ public class PcodeDataTypeManager { } } - /** - * Build an XML document string representing the type information for a data type - * - * @param resBuf is the stream to append the document to - * @param type data type to build XML for - * @param size size of the data type - */ - public void buildType(StringBuilder resBuf, DataType type, int size) { - if (type != null && type.getDataTypeManager() != progDataTypes) { - type = type.clone(progDataTypes); - } - if ((type instanceof VoidDataType) || (type == null)) { - resBuf.append(""); - return; - } - else if (type instanceof TypeDef) { - resBuf.append("'); - DataType refType = ((TypeDef) type).getDataType(); - int sz = refType.getLength(); - if (sz <= 0) { - sz = size; - } - buildTypeRef(resBuf, refType, sz); - resBuf.append(""); - return; - } - buildTypeInternal(resBuf, type, size); - } - /** * Build an XML document string representing the Structure that has * its size reported as zero. diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/SettingsBuilder.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/SettingsBuilder.java index eb9cd3207e..39b02d70b9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/SettingsBuilder.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/SettingsBuilder.java @@ -79,11 +79,6 @@ public class SettingsBuilder implements Settings { return settings.getString(name); } - @Override - public byte[] getByteArray(String name) { - return settings.getByteArray(name); - } - @Override public Object getValue(String name) { return settings.getValue(name); @@ -99,11 +94,6 @@ public class SettingsBuilder implements Settings { settings.setString(name, value); } - @Override - public void setByteArray(String name, byte[] value) { - settings.setByteArray(name, value); - } - @Override public void setValue(String name, Object value) { settings.setValue(name, value); diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/StubDataType.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/StubDataType.java index a4ee1c60a3..4a803fa912 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/StubDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/StubDataType.java @@ -16,7 +16,6 @@ package ghidra.program.model.data; import java.net.URL; -import java.nio.ByteBuffer; import java.util.Collection; import ghidra.docking.settings.Settings; @@ -59,6 +58,11 @@ public class StubDataType implements DataType { throw new UnsupportedOperationException(); } + @Override + public TypeDefSettingsDefinition[] getTypeDefSettingsDefinitions() { + throw new UnsupportedOperationException(); + } + @Override public Settings getDefaultSettings() { throw new UnsupportedOperationException(); @@ -234,11 +238,6 @@ public class StubDataType implements DataType { throw new UnsupportedOperationException(); } - @Override - public void setDefaultSettings(Settings settings) { - throw new UnsupportedOperationException(); - } - @Override public void addParent(DataType dt) { throw new UnsupportedOperationException(); diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/TestDoubleDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/TestDoubleDataTypeManager.java index bb5969fc34..f5687c7a1c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/TestDoubleDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/TestDoubleDataTypeManager.java @@ -345,4 +345,14 @@ public class TestDoubleDataTypeManager implements DataTypeManager { public Set getDataTypesContaining(DataType dataType) { throw new UnsupportedOperationException(); } + + @Override + public boolean allowsDefaultBuiltInSettings() { + return false; + } + + @Override + public boolean allowsDefaultComponentSettings() { + return false; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/TestDummyDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/TestDummyDataTypeManager.java index 140ca5282b..5dbb81a14d 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/TestDummyDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/TestDummyDataTypeManager.java @@ -401,4 +401,16 @@ public class TestDummyDataTypeManager implements DataTypeManager { return null; } + @Override + public boolean allowsDefaultBuiltInSettings() { + // stub + return false; + } + + @Override + public boolean allowsDefaultComponentSettings() { + // stub + return false; + } + } diff --git a/Ghidra/Processors/JVM/src/main/java/ghidra/javaclass/analyzers/AbstractJavaAnalyzer.java b/Ghidra/Processors/JVM/src/main/java/ghidra/javaclass/analyzers/AbstractJavaAnalyzer.java index 734281bff5..ec52601543 100644 --- a/Ghidra/Processors/JVM/src/main/java/ghidra/javaclass/analyzers/AbstractJavaAnalyzer.java +++ b/Ghidra/Processors/JVM/src/main/java/ghidra/javaclass/analyzers/AbstractJavaAnalyzer.java @@ -21,7 +21,8 @@ import ghidra.app.cmd.data.CreateStringCmd; import ghidra.app.cmd.function.CreateFunctionCmd; import ghidra.app.services.Analyzer; import ghidra.app.util.importer.MessageLog; -import ghidra.docking.settings.*; +import ghidra.docking.settings.FormatSettingsDefinition; +import ghidra.docking.settings.SettingsDefinition; import ghidra.framework.options.Options; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; @@ -138,8 +139,6 @@ public abstract class AbstractJavaAnalyzer implements Analyzer { } protected void changeFormatToString(Data data) { - SettingsImpl settings = new SettingsImpl(data); - settings.setDefaultSettings(settings); SettingsDefinition[] settingsDefinitions = data.getDataType().getSettingsDefinitions(); for (SettingsDefinition settingsDefinition : settingsDefinitions) { if (settingsDefinition instanceof FormatSettingsDefinition) {