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 8944f97b2c..49de12948f 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 @@ -46,6 +46,7 @@ import ghidra.docking.settings.SettingsDefinition; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.*; import ghidra.program.model.data.*; +import ghidra.program.model.data.Enum; import ghidra.program.model.listing.*; import ghidra.program.util.*; import ghidra.test.*; @@ -1264,6 +1265,8 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra assertNotNull("Expected data type: " + dtName, d); assertTrue("Expected data type: " + dtName, expectedDataType.isInstance(d.getDataType())); + DataType dt = d.getDataType(); + boolean onByteWordData = true; boolean onFloatDoubleData = true; boolean onCharData = true; @@ -1280,7 +1283,7 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra // || expectedDataType.equals(StringDataType.class) // || expectedDataType.equals(UnicodeDataType.class)); - boolean hasSettings = d.getDataType().getSettingsDefinitions().length != 0; + boolean hasSettings = dt.getSettingsDefinitions().length != 0; String caseName = "On " + dtName + " at: " + getCurrentLocation(); @@ -1294,7 +1297,8 @@ public abstract class AbstractDataActionTest extends AbstractGhidraHeadedIntegra checkAction(actions, CREATE_STRUCTURE, hasSelection, caseName); checkAction(actions, EDIT_DATA_TYPE, - pdata != null && (pdata.isStructure() || pdata.isUnion()), caseName); + (pdata != null && (pdata.isStructure() || pdata.isUnion())) || (dt instanceof Enum), + caseName); checkAction(actions, CREATE_ARRAY, true, caseName); checkAction(actions, DEFAULT_DATA_SETTINGS, (!hasSelection || isSelectionJustSingleDataInstance(sel, d)) && hasSettings, caseName); 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 new file mode 100644 index 0000000000..bcfa2db9bc --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EnumDataActionTest.java @@ -0,0 +1,65 @@ +/* ### + * 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 org.junit.Before; +import org.junit.Test; + +import ghidra.program.model.data.EnumDataType; + +public class EnumDataActionTest extends AbstractDataActionTest { + + private EnumDataType testEnum; + private DataPlugin dataPlugin; + private TestEnumDataAction enumDataAction; + + @Override + @Before + public void setUp() throws Exception { + + super.setUp(); + + testEnum = new EnumDataType("TEST_ENUM", 2); + testEnum.add("A", 0); + testEnum.add("B", 1); + testEnum.add("C", 2); + testEnum.add("D", 3); + testEnum.add("E", 3); + + dataPlugin = getPlugin(tool, DataPlugin.class); + + enumDataAction = new TestEnumDataAction(dataPlugin); + tool.addAction(enumDataAction); + + } + + @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); + } + + class TestEnumDataAction extends DataAction { + + public TestEnumDataAction(DataPlugin plugin) { + super(testEnum, plugin); + } + } + +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/EnumTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/EnumTest.java index 1d1cba2782..624867f222 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/EnumTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/EnumTest.java @@ -44,22 +44,22 @@ public class EnumTest extends AbstractGhidraHeadedIntegrationTest { super(); } - @Before - public void setUp() throws Exception { + @Before + public void setUp() throws Exception { program = createDefaultProgram("Test", ProgramBuilder._TOY, this); dataMgr = program.getDataTypeManager(); transactionID = program.startTransaction("Test"); } - @After - public void tearDown() throws Exception { + @After + public void tearDown() throws Exception { program.endTransaction(transactionID, false); program.release(this); } -@Test - public void testCreateEnum() throws Exception { + @Test + public void testCreateEnum() throws Exception { Enum enumm = new EnumDataType("Color", 1); enumm.add("Red", 0); enumm.add("Green", 1); @@ -88,29 +88,48 @@ public class EnumTest extends AbstractGhidraHeadedIntegrationTest { assertNotNull(c.getDataType("Color")); } -@Test - public void testRemoveValue() throws Exception { + @Test + public void testRemoveValue() throws Exception { Enum enumm = new EnumDataType("Color", 1); enumm.add("Red", 0); enumm.add("Green", 1); enumm.add("Blue", 2); + enumm.add("blue", 2); Category root = dataMgr.getRootCategory(); Category c = root.createCategory("enumms"); enumm.setCategoryPath(c.getCategoryPath()); Enum enummDT = (Enum) dataMgr.resolve(enumm, null); - assertEquals(3, enummDT.getCount()); - enummDT.remove("Green"); - assertEquals(2, enummDT.getCount()); + assertArrayEquals(new long[] { 0, 1, 2 }, enumm.getValues()); + assertEquals(4, enumm.getCount()); + + enummDT.remove("Green"); + enummDT.remove("blue"); + + assertEquals(2, enummDT.getCount()); + assertArrayEquals(new long[] { 0, 2 }, enummDT.getValues()); + + assertEquals(2, enummDT.getValue("Blue")); + try { + enummDT.getValue("blue"); + fail("expected NoSuchElementException"); + } + catch (NoSuchElementException e) { + // expected + } } -@Test - public void testAddValue() throws Exception { + @Test + public void testAddValue() throws Exception { Enum enumm = new EnumDataType("Color", 1); enumm.add("Red", 0); enumm.add("Green", 1); enumm.add("Blue", 2); + enumm.add("blue", 2); + + assertArrayEquals(new long[] { 0, 1, 2 }, enumm.getValues()); + assertEquals(4, enumm.getCount()); Category root = dataMgr.getRootCategory(); Category c = root.createCategory("enumms"); @@ -119,12 +138,15 @@ public class EnumTest extends AbstractGhidraHeadedIntegrationTest { Enum enummDT = (Enum) dataMgr.resolve(enumm, null); enummDT.add("Purple", 7); - assertEquals(4, enummDT.getCount()); + assertEquals(5, enummDT.getCount()); assertEquals(7, enummDT.getValue("Purple")); + assertEquals(2, enummDT.getValue("Blue")); + assertEquals(2, enummDT.getValue("blue")); + assertArrayEquals(new long[] { 0, 1, 2, 7 }, enummDT.getValues()); } -@Test - public void testEditValue() throws Exception { + @Test + public void testEditValue() throws Exception { Enum enumm = new EnumDataType("Color", 1); enumm.add("Red", 10); enumm.add("Green", 15); @@ -148,8 +170,8 @@ public class EnumTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(2, listener.getCount()); } -@Test - public void testCloneRetainIdentity() throws Exception { + @Test + public void testCloneRetainIdentity() throws Exception { Enum enumm = new EnumDataType("Color", 1); enumm.add("Red", 10); enumm.add("Green", 15); @@ -168,8 +190,8 @@ public class EnumTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(copyDT.isEquivalent(c2)); } -@Test - public void testCopyNoRetainIdentity() throws Exception { + @Test + public void testCopyNoRetainIdentity() throws Exception { Enum enumm = new EnumDataType("Color", 1); enumm.add("Red", 10); enumm.add("Green", 15); @@ -188,8 +210,8 @@ public class EnumTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(copyDT.isEquivalent(c2)); } -@Test - public void testRemoveEnum() throws Exception { + @Test + public void testRemoveEnum() throws Exception { Enum enumm = new EnumDataType("Color", 1); enumm.add("Red", 10); enumm.add("Green", 15); @@ -208,8 +230,8 @@ public class EnumTest extends AbstractGhidraHeadedIntegrationTest { } -@Test - public void testMoveEnum() throws Exception { + @Test + public void testMoveEnum() throws Exception { Enum enumm = new EnumDataType("Color", 1); enumm.add("Red", 10); enumm.add("Green", 15); @@ -224,8 +246,8 @@ public class EnumTest extends AbstractGhidraHeadedIntegrationTest { assertNull(c.getDataType(enumm.getName())); } -@Test - public void testResolve() throws Exception { + @Test + public void testResolve() throws Exception { Enum enumm = new EnumDataType("Color", 1); enumm.add("Red", 10); enumm.add("Green", 15); @@ -239,8 +261,8 @@ public class EnumTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(enummDT, dataMgr.getDataType(id)); } -@Test - public void testReplace() throws Exception { + @Test + public void testReplace() throws Exception { Enum enumm = new EnumDataType("Color", 1); enumm.add("Red", 10); enumm.add("Green", 15); @@ -275,8 +297,8 @@ public class EnumTest extends AbstractGhidraHeadedIntegrationTest { } } -@Test - public void testIsEquivalent() throws Exception { + @Test + public void testIsEquivalent() throws Exception { Enum enumm = new EnumDataType("Color", 1); enumm.add("Red", 10); enumm.add("Green", 15); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/EnumDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/EnumDB.java index b238a1029f..092edfd5d9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/EnumDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/EnumDB.java @@ -21,6 +21,7 @@ import java.util.*; import db.Record; import ghidra.docking.settings.Settings; +import ghidra.docking.settings.SettingsDefinition; import ghidra.program.database.DBObjectCache; import ghidra.program.model.data.*; import ghidra.program.model.data.Enum; @@ -28,9 +29,6 @@ import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.scalar.Scalar; import ghidra.util.UniversalID; -import ghidra.util.datastruct.LongObjectHashtable; -import ghidra.util.datastruct.ObjectLongHashtable; -import ghidra.util.exception.NoValueException; /** * Database implementation for the enumerated data type. @@ -38,10 +36,13 @@ import ghidra.util.exception.NoValueException; */ class EnumDB extends DataTypeDB implements Enum { + private static final SettingsDefinition[] ENUM_SETTINGS_DEFINITIONS = + new SettingsDefinition[] { MutabilitySettingsDefinition.DEF }; + private EnumDBAdapter adapter; private EnumValueDBAdapter valueAdapter; - private ObjectLongHashtable nameMap; - private LongObjectHashtable valueMap; + private Map nameMap; // name to value + private Map> valueMap; // value to names private List bitGroups; EnumDB(DataTypeManagerDB dataMgr, DBObjectCache cache, EnumDBAdapter adapter, @@ -49,9 +50,6 @@ class EnumDB extends DataTypeDB implements Enum { super(dataMgr, cache, record); this.adapter = adapter; this.valueAdapter = valueAdapter; - nameMap = new ObjectLongHashtable<>(); - valueMap = new LongObjectHashtable<>(); - initialize(); } @Override @@ -64,71 +62,109 @@ class EnumDB extends DataTypeDB implements Enum { return record.getString(EnumDBAdapter.ENUM_NAME_COL); } + @Override + public SettingsDefinition[] getSettingsDefinitions() { + return ENUM_SETTINGS_DEFINITIONS; + } + + private void initializeIfNeeded() { + if (nameMap != null) { + return; + } + try { + initialize(); + } + catch (IOException e) { + dataMgr.dbError(e); + } + } + private void initialize() throws IOException { bitGroups = null; - nameMap.removeAll(); - valueMap.removeAll(); + nameMap = new HashMap<>(); + valueMap = new HashMap<>(); + long[] ids = valueAdapter.getValueIdsInEnum(key); for (int i = 0; i < ids.length; i++) { Record rec = valueAdapter.getRecord(ids[i]); String valueName = rec.getString(EnumValueDBAdapter.ENUMVAL_NAME_COL); long value = rec.getLongValue(EnumValueDBAdapter.ENUMVAL_VALUE_COL); - nameMap.put(valueName, value); - valueMap.put(value, valueName); + addToCache(valueName, value); } } - /** - * @see ghidra.program.model.data.Enum#getValue(java.lang.String) - */ + private void addToCache(String valueName, long value) { + nameMap.put(valueName, value); + List list = valueMap.computeIfAbsent(value, v -> new ArrayList<>()); + list.add(valueName); + } + + private boolean removeFromCache(String valueName) { + Long value = nameMap.remove(valueName); + if (value == null) { + return false; + } + List list = valueMap.get(value); + Iterator iter = list.iterator(); + while (iter.hasNext()) { + if (valueName.equals(iter.next())) { + iter.remove(); + break; + } + } + if (list.isEmpty()) { + valueMap.remove(value); + } + return true; + } + @Override - public long getValue(String name) throws NoSuchElementException { + public long getValue(String valueName) throws NoSuchElementException { lock.acquire(); try { checkIsValid(); - return nameMap.get(name); - } - catch (NoValueException e) { - throw new NoSuchElementException(name + " does not exist in this enum"); + initializeIfNeeded(); + Long value = nameMap.get(valueName); + if (value == null) { + throw new NoSuchElementException("No value for " + valueName); + } + return value; } finally { lock.release(); } } - /** - * @see ghidra.program.model.data.Enum#getName(long) - */ @Override public String getName(long value) { lock.acquire(); try { checkIsValid(); - return valueMap.get(value); + initializeIfNeeded(); + List list = valueMap.get(value); + if (list == null || list.isEmpty()) { + return null; + } + return list.get(0); } finally { lock.release(); } } - /** - * @see ghidra.program.model.data.DataType#isDynamicallySized() - */ @Override public boolean isDynamicallySized() { return false; } - /** - * @see ghidra.program.model.data.Enum#getValues() - */ @Override public long[] getValues() { lock.acquire(); try { checkIsValid(); - long[] values = valueMap.getKeys(); + initializeIfNeeded(); + long[] values = valueMap.keySet().stream().mapToLong(Long::longValue).toArray(); Arrays.sort(values); return values; } @@ -137,31 +173,25 @@ class EnumDB extends DataTypeDB implements Enum { } } - /** - * @see ghidra.program.model.data.Enum#getNames() - */ @Override public String[] getNames() { lock.acquire(); try { checkIsValid(); - String[] names = nameMap.getKeys(new String[nameMap.size()]); - Arrays.sort(names); - return names; + initializeIfNeeded(); + return nameMap.keySet().toArray(new String[nameMap.size()]); } finally { lock.release(); } } - /** - * @see ghidra.program.model.data.Enum#getCount() - */ @Override public int getCount() { lock.acquire(); try { checkIsValid(); + initializeIfNeeded(); return nameMap.size(); } finally { @@ -169,58 +199,62 @@ class EnumDB extends DataTypeDB implements Enum { } } - /** - * @see ghidra.program.model.data.Enum#add(java.lang.String, long) - */ @Override - public void add(String name, long value) { + public void add(String valueName, long value) { lock.acquire(); - bitGroups = null; try { checkDeleted(); - if (nameMap.contains(name)) { - throw new IllegalArgumentException(name + " already exists in this enum"); - } - try { - valueAdapter.createRecord(key, name, value); - nameMap.put(name, value); - if (!valueMap.contains(value)) { - valueMap.put(value, name); - } - adapter.updateRecord(record, true); - dataMgr.dataTypeChanged(this); - } - catch (IOException e) { - dataMgr.dbError(e); + checkValue(value); + initializeIfNeeded(); + if (nameMap.containsKey(valueName)) { + throw new IllegalArgumentException(valueName + " already exists in this enum"); } + bitGroups = null; + valueAdapter.createRecord(key, valueName, value); + adapter.updateRecord(record, true); + addToCache(valueName, value); + dataMgr.dataTypeChanged(this); + + } + catch (IOException e) { + dataMgr.dbError(e); } finally { lock.release(); } } - /** - * @see ghidra.program.model.data.Enum#remove(java.lang.String) - */ + private void checkValue(long value) { + int length = getLength(); + if (length == 8) { + return; // all long values permitted + } + // compute maximum enum value as a positive value: (2^length)-1 + long max = (1L << (getLength() * 8)) - 1; + if (value > max) { + throw new IllegalArgumentException( + getName() + " enum value 0x" + Long.toHexString(value) + + " is outside the range of 0x0 to 0x" + Long.toHexString(max)); + + } + } + @Override - public void remove(String name) { + public void remove(String valueName) { lock.acquire(); - bitGroups = null; try { checkDeleted(); - if (!nameMap.contains(name)) { + initializeIfNeeded(); + if (!removeFromCache(valueName)) { return; } - long value = nameMap.get(name); - nameMap.remove(name); - if (name.equals(valueMap.get(value))) { - valueMap.remove(value); - } + bitGroups = null; + long[] ids = valueAdapter.getValueIdsInEnum(key); for (int i = 0; i < ids.length; i++) { Record rec = valueAdapter.getRecord(ids[i]); - if (name.equals(rec.getString(EnumValueDBAdapter.ENUMVAL_NAME_COL))) { + if (valueName.equals(rec.getString(EnumValueDBAdapter.ENUMVAL_NAME_COL))) { valueAdapter.removeRecord(ids[i]); break; } @@ -231,17 +265,11 @@ class EnumDB extends DataTypeDB implements Enum { catch (IOException e) { dataMgr.dbError(e); } - catch (NoValueException e) { - // can't happen - } finally { lock.release(); } } - /* - * @see ghidra.program.model.data.Enum#replace(ghidra.program.model.data.Enum) - */ @Override public void replaceWith(DataType dataType) { if (!(dataType instanceof Enum)) { @@ -249,30 +277,33 @@ class EnumDB extends DataTypeDB implements Enum { } Enum enumm = (Enum) dataType; lock.acquire(); - bitGroups = null; try { checkDeleted(); - int oldLength = getLength(); - nameMap.removeAll(); - valueMap.removeAll(); + + bitGroups = null; + nameMap = new HashMap<>(); + valueMap = new HashMap<>(); + long[] ids = valueAdapter.getValueIdsInEnum(key); for (int i = 0; i < ids.length; i++) { valueAdapter.removeRecord(ids[i]); } - String[] names = enumm.getNames(); - for (int i = 0; i < names.length; i++) { - if (nameMap.contains(names[i])) { - throw new IllegalArgumentException(names[i] + " already exists in this Enum"); - } - long value = enumm.getValue(names[i]); - valueAdapter.createRecord(key, names[i], value); - nameMap.put(names[i], value); - valueMap.put(value, names[i]); + + int oldLength = getLength(); + int newLength = enumm.getLength(); + + if (oldLength != newLength) { + record.setByteValue(EnumDBAdapter.ENUM_SIZE_COL, (byte) newLength); + adapter.updateRecord(record, true); } - int newLength = enumm.getLength(); - record.setByteValue(EnumDBAdapter.ENUM_SIZE_COL, (byte) newLength); - adapter.updateRecord(record, true); + String[] names = enumm.getNames(); + for (int i = 0; i < names.length; i++) { + long value = enumm.getValue(names[i]); + valueAdapter.createRecord(key, names[i], value); + adapter.updateRecord(record, true); + addToCache(names[i], value); + } if (oldLength != newLength) { notifySizeChanged(); @@ -308,9 +339,6 @@ class EnumDB extends DataTypeDB implements Enum { return enumDataType; } - /** - * @see ghidra.program.model.data.DataType#getMnemonic(ghidra.docking.settings.Settings) - */ @Override public String getMnemonic(Settings settings) { lock.acquire(); @@ -323,9 +351,6 @@ class EnumDB extends DataTypeDB implements Enum { } } - /** - * @see ghidra.program.model.data.DataType#getLength() - */ @Override public int getLength() { lock.acquire(); @@ -338,9 +363,6 @@ class EnumDB extends DataTypeDB implements Enum { } } - /** - * @see ghidra.program.model.data.DataType#getDescription() - */ @Override public String getDescription() { lock.acquire(); @@ -354,9 +376,6 @@ class EnumDB extends DataTypeDB implements Enum { } } - /** - * @see ghidra.program.model.data.Enum#setDescription(java.lang.String) - */ @Override public void setDescription(String description) { lock.acquire(); @@ -374,9 +393,6 @@ class EnumDB extends DataTypeDB implements Enum { } } - /** - * @see ghidra.program.model.data.DataType#getValue(ghidra.program.model.mem.MemBuffer, ghidra.docking.settings.Settings, int) - */ @Override public Object getValue(MemBuffer buf, Settings settings, int length) { lock.acquire(); @@ -412,9 +428,6 @@ class EnumDB extends DataTypeDB implements Enum { return Scalar.class; } - /** - * @see ghidra.program.model.data.DataType#getRepresentation(ghidra.program.model.mem.MemBuffer, ghidra.docking.settings.Settings, int) - */ @Override public String getRepresentation(MemBuffer buf, Settings settings, int length) { lock.acquire(); @@ -501,9 +514,6 @@ class EnumDB extends DataTypeDB implements Enum { return valueName; } - /** - * @see ghidra.program.model.data.DataType#isEquivalent(ghidra.program.model.data.DataType) - */ @Override public boolean isEquivalent(DataType dt) { if (dt == this) { @@ -513,7 +523,6 @@ class EnumDB extends DataTypeDB implements Enum { return false; } - checkIsValid(); Enum enumm = (Enum) dt; if (!DataTypeUtilities.equalsIgnoreConflict(getName(), enumm.getName()) || getLength() != enumm.getLength() || getCount() != enumm.getCount()) { @@ -536,16 +545,15 @@ class EnumDB extends DataTypeDB implements Enum { return true; } - /** - * @see ghidra.program.database.DatabaseObject#refresh() - */ @Override protected boolean refresh() { try { + nameMap = null; + valueMap = null; + bitGroups = null; Record rec = adapter.getRecord(key); if (rec != null) { record = rec; - initialize(); return super.refresh(); } } @@ -555,9 +563,6 @@ class EnumDB extends DataTypeDB implements Enum { return false; } - /** - * @see ghidra.program.model.data.DataType#dataTypeReplaced(ghidra.program.model.data.DataType, ghidra.program.model.data.DataType) - */ @Override public void dataTypeReplaced(DataType oldDt, DataType newDt) { // not applicable @@ -575,17 +580,11 @@ class EnumDB extends DataTypeDB implements Enum { adapter.updateRecord(record, true); } - /** - * @see ghidra.program.model.data.DataType#dataTypeDeleted(ghidra.program.model.data.DataType) - */ @Override public void dataTypeDeleted(DataType dt) { // not applicable } - /** - * @see ghidra.program.model.data.DataType#dataTypeNameChanged(ghidra.program.model.data.DataType, java.lang.String) - */ @Override public void dataTypeNameChanged(DataType dt, String oldName) { // not applicable 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 7bc4edd9f7..6965017dd5 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 @@ -33,9 +33,11 @@ import ghidra.util.exception.DuplicateNameException; */ class PointerDB extends DataTypeDB implements Pointer { + private static final SettingsDefinition[] POINTER_SETTINGS_DEFINITIONS = + new SettingsDefinition[] { MutabilitySettingsDefinition.DEF }; + private PointerDBAdapter adapter; private String displayName; - private SettingsDefinition[] settingsDef; /** * Constructor @@ -74,9 +76,6 @@ class PointerDB extends DataTypeDB implements Pointer { return pointerName; } - /** - * @see ghidra.program.model.data.Pointer#getDataType() - */ @Override public DataType getDataType() { lock.acquire(); @@ -91,18 +90,7 @@ class PointerDB extends DataTypeDB implements Pointer { @Override public SettingsDefinition[] getSettingsDefinitions() { - lock.acquire(); - try { - checkIsValid(); - if (settingsDef == null) { - DataType dt = newPointer(getDataType()); - settingsDef = dt.getSettingsDefinitions(); - } - return settingsDef; - } - finally { - lock.release(); - } + return POINTER_SETTINGS_DEFINITIONS; } @Override @@ -126,9 +114,6 @@ class PointerDB extends DataTypeDB implements Pointer { return false; } - /** - * @see ghidra.program.model.data.DataType#clone(ghidra.program.model.data.DataTypeManager) - */ @Override public final DataType clone(DataTypeManager dtm) { if (dtm == getDataTypeManager()) { @@ -143,9 +128,6 @@ class PointerDB extends DataTypeDB implements Pointer { return clone(dtm); } - /** - * @see ghidra.program.model.data.DataType#getName() - */ @Override public String getDisplayName() { validate(lock); @@ -166,9 +148,6 @@ class PointerDB extends DataTypeDB implements Pointer { return localDisplayName; } - /** - * @see ghidra.program.model.data.DataType#getMnemonic(ghidra.docking.settings.Settings) - */ @Override public String getMnemonic(Settings settings) { lock.acquire(); @@ -185,9 +164,6 @@ class PointerDB extends DataTypeDB implements Pointer { } } - /** - * @see ghidra.program.model.data.Pointer#isDynamicallySized() - */ @Override public boolean isDynamicallySized() { lock.acquire(); @@ -200,9 +176,6 @@ class PointerDB extends DataTypeDB implements Pointer { } } - /** - * @see ghidra.program.model.data.DataType#getLength() - */ @Override public int getLength() { lock.acquire(); @@ -219,9 +192,6 @@ class PointerDB extends DataTypeDB implements Pointer { } } - /** - * @see ghidra.program.model.data.DataType#getDescription() - */ @Override public String getDescription() { lock.acquire(); @@ -250,9 +220,6 @@ class PointerDB extends DataTypeDB implements Pointer { } } - /** - * @see ghidra.program.model.data.DataType#getValue(ghidra.program.model.mem.MemBuffer, ghidra.docking.settings.Settings, int) - */ @Override public Object getValue(MemBuffer buf, Settings settings, int length) { lock.acquire(); @@ -278,9 +245,6 @@ class PointerDB extends DataTypeDB implements Pointer { return Address.class; } - /** - * @see ghidra.program.model.data.DataType#getRepresentation(ghidra.program.model.mem.MemBuffer, ghidra.docking.settings.Settings, int) - */ @Override public String getRepresentation(MemBuffer buf, Settings settings, int length) { lock.acquire(); @@ -298,9 +262,6 @@ class PointerDB extends DataTypeDB implements Pointer { } } - /** - * @see ghidra.program.model.data.DataType#isEquivalent(ghidra.program.model.data.DataType) - */ @Override public boolean isEquivalent(DataType dt) { if (dt == null) { @@ -339,9 +300,6 @@ class PointerDB extends DataTypeDB implements Pointer { otherDataType.getPathName()); } - /** - * @see ghidra.program.model.data.DataType#dataTypeReplaced(ghidra.program.model.data.DataType, ghidra.program.model.data.DataType) - */ @Override public void dataTypeReplaced(DataType oldDt, DataType newDt) { if (newDt == this) { @@ -371,9 +329,6 @@ class PointerDB extends DataTypeDB implements Pointer { } } - /** - * @see ghidra.program.model.data.DataType#dataTypeDeleted(ghidra.program.model.data.DataType) - */ @Override public void dataTypeDeleted(DataType dt) { if (getDataType() == dt) { @@ -440,26 +395,17 @@ class PointerDB extends DataTypeDB implements Pointer { } } - /** - * @see ghidra.program.model.data.DataType#setCategoryPath(ghidra.program.model.data.CategoryPath) - */ @Override public void setCategoryPath(CategoryPath path) throws DuplicateNameException { // not permitted to move - follows base type (see updatePath) } - /** - * @see ghidra.program.model.data.DataType#dependsOn(ghidra.program.model.data.DataType) - */ @Override public boolean dependsOn(DataType dt) { DataType myDt = getDataType(); return (myDt != null && (myDt == dt || myDt.dependsOn(dt))); } - /** - * @see ghidra.program.model.data.Pointer#newPointer(ghidra.program.model.data.DataType) - */ @Override public Pointer newPointer(DataType dataType) { if (isDynamicallySized()) { 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 5ebe7b7245..7595f3ba59 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 @@ -32,7 +32,7 @@ import ghidra.util.exception.DuplicateNameException; */ public abstract class BuiltIn extends DataTypeImpl implements BuiltInDataType { - private static SettingsDefinition[] STANDARD_SETTINGS_DEFINITIONS = + private static final SettingsDefinition[] STANDARD_SETTINGS_DEFINITIONS = new SettingsDefinition[] { MutabilitySettingsDefinition.DEF }; private SettingsDefinition[] settingDefs; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/EnumDataType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/EnumDataType.java index c18320a892..d3beeab305 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/EnumDataType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/EnumDataType.java @@ -20,17 +20,20 @@ import java.util.*; import ghidra.app.plugin.core.datamgr.archive.SourceArchive; import ghidra.docking.settings.Settings; +import ghidra.docking.settings.SettingsDefinition; import ghidra.program.database.data.DataTypeUtilities; import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.scalar.Scalar; import ghidra.util.UniversalID; -import ghidra.util.datastruct.LongObjectHashtable; -import ghidra.util.datastruct.ObjectLongHashtable; -import ghidra.util.exception.NoValueException; public class EnumDataType extends GenericDataType implements Enum { - private ObjectLongHashtable defs; + + private static final SettingsDefinition[] ENUM_SETTINGS_DEFINITIONS = + new SettingsDefinition[] { MutabilitySettingsDefinition.DEF }; + + private Map nameMap; // name to value + private Map> valueMap; // value to names private int length; private String description; private List bitGroups; @@ -45,7 +48,11 @@ public class EnumDataType extends GenericDataType implements Enum { public EnumDataType(CategoryPath path, String name, int length, DataTypeManager dtm) { super(path, name, dtm); - defs = new ObjectLongHashtable<>(); + if (length < 1 || length > 8) { + throw new IllegalArgumentException("unsupported enum length: " + length); + } + nameMap = new HashMap<>(); + valueMap = new HashMap<>(); this.length = length; } @@ -54,13 +61,19 @@ public class EnumDataType extends GenericDataType implements Enum { DataTypeManager dtm) { super(path, name, universalID, sourceArchive, lastChangeTime, lastChangeTimeInSourceArchive, dtm); - defs = new ObjectLongHashtable<>(); + if (length < 1 || length > 8) { + throw new IllegalArgumentException("unsupported enum length: " + length); + } + nameMap = new HashMap<>(); + valueMap = new HashMap<>(); this.length = length; } - /** - * @see ghidra.program.model.data.DataType#isDynamicallySized() - */ + @Override + public SettingsDefinition[] getSettingsDefinitions() { + return ENUM_SETTINGS_DEFINITIONS; + } + @Override public boolean isDynamicallySized() { return false; @@ -68,91 +81,96 @@ public class EnumDataType extends GenericDataType implements Enum { @Override public long getValue(String valueName) throws NoSuchElementException { - try { - return defs.get(valueName); - } - catch (NoValueException e) { + Long value = nameMap.get(valueName); + if (value == null) { throw new NoSuchElementException("No value for " + valueName); } + return value; } @Override public String getName(long value) { - String[] names = defs.getKeys(new String[defs.size()]); - for (String name1 : names) { - try { - long nameValue = defs.get(name1); - if (nameValue == value) { - return name1; - } - } - catch (NoValueException e) { - // can't happen - } + List list = valueMap.get(value); + if (list == null || list.isEmpty()) { + return null; } - return null; + return list.get(0); } @Override public long[] getValues() { - String[] names = defs.getKeys(new String[defs.size()]); - LongObjectHashtable keyTable = new LongObjectHashtable<>(); - for (String name1 : names) { - try { - long value = defs.get(name1); - keyTable.put(value, name1); - } - catch (NoValueException e) { - // can't happen - } - } - long[] values = keyTable.getKeys(); + long[] values = valueMap.keySet().stream().mapToLong(Long::longValue).toArray(); Arrays.sort(values); return values; } @Override public String[] getNames() { - String[] names = defs.getKeys(new String[defs.size()]); - Arrays.sort(names); - return names; + return nameMap.keySet().toArray(new String[nameMap.size()]); } @Override public int getCount() { - return defs.size(); + return nameMap.size(); } @Override public void add(String valueName, long value) { bitGroups = null; checkValue(value); - if (defs.contains(valueName)) { - try { - if (defs.get(valueName) == value) { - return; - } - } - catch (NoValueException e) { - } - throw new IllegalArgumentException(name + " enum value " + value + " already assigned"); + if (nameMap.containsKey(valueName)) { + throw new IllegalArgumentException(name + " already exists in this enum"); } - defs.put(valueName, value); + nameMap.put(valueName, value); + List list = valueMap.get(value); + if (list == null) { + list = new ArrayList<>(); + valueMap.put(value, list); + } + list.add(valueName); } private void checkValue(long value) { - long max = (1L << (length * 8)) - 1; - if (max > 0 && value > max) { - throw new IllegalArgumentException(name + " enum value 0x" + Long.toHexString(value) + - " is outside the range of 0x0 to 0x" + Long.toHexString(max)); + if (length == 8) { + return; // all long values permitted + } + // compute maximum enum value as a positive value: (2^length)-1 + long max = (1L << (getLength() * 8)) - 1; + if (value > max) { + throw new IllegalArgumentException( + getName() + " enum value 0x" + Long.toHexString(value) + + " is outside the range of 0x0 to 0x" + Long.toHexString(max)); } } + private boolean isTooBig(int testLength, long value) { + if (length == 8) { + return false; // all long values permitted + } + // compute maximum enum value as a positive value: (2^length)-1 + long max = (1L << (testLength * 8)) - 1; + return value > max; + } + @Override public void remove(String valueName) { bitGroups = null; - defs.remove(valueName); + Long value = nameMap.get(valueName); + if (value != null) { + nameMap.remove(valueName); + List list = valueMap.get(value); + Iterator iter = list.iterator(); + while (iter.hasNext()) { + if (valueName.equals(iter.next())) { + iter.remove(); + break; + } + } + if (list.isEmpty()) { + valueMap.remove(value); + } + } } @Override @@ -200,14 +218,6 @@ public class EnumDataType extends GenericDataType implements Enum { this.length = length; } - private boolean isTooBig(int testLength, long value) { - long max = (1L << (testLength * 8)) - 1; - if (max > 0 && value > max) { - return true; - } - return false; - } - @Override public String getDescription() { return description == null ? "" : description; @@ -368,46 +378,36 @@ public class EnumDataType extends GenericDataType implements Enum { throw new IllegalArgumentException(); } Enum enumm = (Enum) dataType; - defs.removeAll(); + nameMap = new HashMap<>(); + valueMap = new HashMap<>(); setLength(enumm.getLength()); String[] names = enumm.getNames(); for (int i = 0; i < names.length; i++) { - defs.put(names[i], enumm.getValue(names[i])); + add(names[i], enumm.getValue(names[i])); } stateChanged(null); } - /** - * @see ghidra.program.model.data.DataType#dataTypeSizeChanged(ghidra.program.model.data.DataType) - */ @Override public void dataTypeSizeChanged(DataType dt) { + // not applicable } - /** - * @see ghidra.program.model.data.DataType#dataTypeDeleted(ghidra.program.model.data.DataType) - */ @Override public void dataTypeDeleted(DataType dt) { + // not applicable } - /** - * @see ghidra.program.model.data.DataType#dataTypeNameChanged(ghidra.program.model.data.DataType, java.lang.String) - */ @Override public void dataTypeNameChanged(DataType dt, String oldName) { + // not applicable } - /** - * @see ghidra.program.model.data.DataType#dataTypeReplaced(ghidra.program.model.data.DataType, ghidra.program.model.data.DataType) - */ @Override public void dataTypeReplaced(DataType oldDt, DataType newDt) { + // not applicable } - /** - * @see ghidra.program.model.data.DataType#dependsOn(ghidra.program.model.data.DataType) - */ @Override public boolean dependsOn(DataType dt) { return false; diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/EnumDataTypeTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/EnumDataTypeTest.java index 5b46425fa2..d68b105bf2 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/EnumDataTypeTest.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/data/EnumDataTypeTest.java @@ -17,15 +17,12 @@ package ghidra.program.model.data; import org.junit.*; -import ghidra.program.model.mem.MemBuffer; -import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.model.address.Address; +import ghidra.program.model.mem.ByteMemBufferImpl; +import ghidra.util.BigEndianDataConverter; import ghidra.util.UniversalIdGenerator; -import mockit.Expectations; -import mockit.Mocked; public class EnumDataTypeTest { - @Mocked - MemBuffer memBuffer; @Before public void setUp() { @@ -33,33 +30,27 @@ public class EnumDataTypeTest { } @Test - public void testNegativeValue() throws MemoryAccessException { - new Expectations() { - { - memBuffer.getInt(anyInt); - result = 0xffffffff; - } - }; + public void testNegativeValue() { EnumDataType enumDt = new EnumDataType("Test", 4); enumDt.add("bob", 0xffffffffL); + ByteMemBufferImpl memBuffer = new ByteMemBufferImpl(Address.NO_ADDRESS, + BigEndianDataConverter.INSTANCE.getBytes(-1), true); + Assert.assertEquals("bob", enumDt.getRepresentation(memBuffer, null, 0)); } @Test - public void testUpperBitLongValue() throws MemoryAccessException { - new Expectations() { - { - memBuffer.getInt(anyInt); - result = 0x80000000; - } - }; + public void testUpperBitLongValue() { EnumDataType enumDt = new EnumDataType("Test", 4); enumDt.add("bob", 0x80000000L); + ByteMemBufferImpl memBuffer = new ByteMemBufferImpl(Address.NO_ADDRESS, + BigEndianDataConverter.INSTANCE.getBytes(Integer.MIN_VALUE), true); + Assert.assertEquals("bob", enumDt.getRepresentation(memBuffer, null, 0)); }