Merge remote-tracking branch 'origin/GP-1870_ghidravore_add_simple_string_properties_to_user_data--SQUASHED'

This commit is contained in:
Ryan Kurtz 2022-03-31 00:53:08 -04:00
commit 0fd9dddceb
4 changed files with 236 additions and 140 deletions

View file

@ -19,6 +19,7 @@ import static org.junit.Assert.*;
import java.io.File; import java.io.File;
import java.util.Random; import java.util.Random;
import java.util.Set;
import org.junit.*; import org.junit.*;
@ -142,52 +143,42 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest {
DomainFile df2; DomainFile df2;
Program program = Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
(Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
space = program.getAddressFactory().getDefaultAddressSpace(); space = program.getAddressFactory().getDefaultAddressSpace();
try { assertFalse(program.isChanged());
assertFalse(program.isChanged()); assertTrue(program.canSave());
assertTrue(program.canSave());
// Modify program content - no user data should be saved // Modify program content - no user data should be saved
change(program); change(program);
assertTrue(program.isChanged()); assertTrue(program.isChanged());
program.save("save", TaskMonitor.DUMMY); program.save("save", TaskMonitor.DUMMY);
assertFalse(program.isChanged()); assertFalse(program.isChanged());
assertFalse("User data directory should be empty", userDataSubDir.isDirectory()); assertFalse("User data directory should be empty", userDataSubDir.isDirectory());
// Modify user data content // Modify user data content
ProgramUserData userData = program.getProgramUserData(); ProgramUserData userData = program.getProgramUserData();
change(userData, "STRING", space.getAddress(0), "Str0"); change(userData, "STRING", space.getAddress(0), "Str0");
change(userData, "STRING", space.getAddress(10), "Str10"); change(userData, "STRING", space.getAddress(10), "Str10");
assertFalse(program.isChanged()); assertFalse(program.isChanged());
String newName = df.getName() + ".1"; String newName = df.getName() + ".1";
df2 = df.getParent().createFile(newName, program, TaskMonitor.DUMMY); df2 = df.getParent().createFile(newName, program, TaskMonitor.DUMMY);
} program.release(this);
finally {
program.release(this);
}
program = program = (Program) df2.getDomainObject(this, false, false, TaskMonitor.DUMMY);
(Program) df2.getDomainObject(this, false, false, TaskMonitor.DUMMY); assertFalse(program.isChanged());
try {
assertFalse(program.isChanged());
// Verify user data content // Verify user data content
ProgramUserData userData = program.getProgramUserData(); userData = program.getProgramUserData();
StringPropertyMap map = StringPropertyMap map =
userData.getStringProperty(testName.getMethodName(), "STRING", false); userData.getStringProperty(testName.getMethodName(), "STRING", false);
assertEquals("Str0", map.getString(space.getAddress(0))); assertEquals("Str0", map.getString(space.getAddress(0)));
assertEquals("Str10", map.getString(space.getAddress(10))); assertEquals("Str10", map.getString(space.getAddress(10)));
assertNull(map.getString(space.getAddress(20))); assertNull(map.getString(space.getAddress(20)));
assertFalse(program.isChanged()); assertFalse(program.isChanged());
}
finally {
program.release(this);
}
program.release(this);
} }
@Test @Test
@ -204,77 +195,63 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest {
File dbDir = new File(dataDir, "00/~00000000.db"); File dbDir = new File(dataDir, "00/~00000000.db");
int ver; int ver;
Program program = Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
(Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
space = program.getAddressFactory().getDefaultAddressSpace(); space = program.getAddressFactory().getDefaultAddressSpace();
try { assertFalse(program.isChanged());
assertFalse(program.isChanged()); assertTrue(program.canSave());
assertTrue(program.canSave());
// Modify program content - no user data should be saved // Modify program content - no user data should be saved
change(program); change(program);
assertTrue(program.isChanged()); assertTrue(program.isChanged());
program.save("save", TaskMonitor.DUMMY); program.save("save", TaskMonitor.DUMMY);
assertFalse(program.isChanged()); assertFalse(program.isChanged());
assertFalse("User data directory should be empty", userDataSubDir.isDirectory()); assertFalse("User data directory should be empty", userDataSubDir.isDirectory());
ver = getLatestDbVersion(dbDir); ver = getLatestDbVersion(dbDir);
// Modify user data content // Modify user data content
ProgramUserData userData = program.getProgramUserData(); ProgramUserData userData = program.getProgramUserData();
change(userData, "STRING", space.getAddress(0), "Str0"); change(userData, "STRING", space.getAddress(0), "Str0");
change(userData, "STRING", space.getAddress(10), "Str10"); change(userData, "STRING", space.getAddress(10), "Str10");
assertFalse(program.isChanged()); assertFalse(program.isChanged());
} program.release(this);
finally {
program.release(this);
}
assertEquals("User data files missing", 2, userDataSubDir.list().length); assertEquals("User data files missing", 2, userDataSubDir.list().length);
assertEquals("Program database should not have been updated", ver, assertEquals("Program database should not have been updated", ver,
getLatestDbVersion(dbDir)); getLatestDbVersion(dbDir));
program = program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
(Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); assertFalse(program.isChanged());
try {
assertFalse(program.isChanged());
// Verify user data content // Verify user data content
ProgramUserData userData = program.getProgramUserData(); userData = program.getProgramUserData();
StringPropertyMap map = StringPropertyMap map =
userData.getStringProperty(testName.getMethodName(), "STRING", false); userData.getStringProperty(testName.getMethodName(), "STRING", false);
assertEquals("Str0", map.getString(space.getAddress(0))); assertEquals("Str0", map.getString(space.getAddress(0)));
assertEquals("Str10", map.getString(space.getAddress(10))); assertEquals("Str10", map.getString(space.getAddress(10)));
assertNull(map.getString(space.getAddress(20))); assertNull(map.getString(space.getAddress(20)));
assertFalse(program.isChanged()); assertFalse(program.isChanged());
// Modify user data content // Modify user data content
change(userData, "STRING", space.getAddress(10), "Str10a"); change(userData, "STRING", space.getAddress(10), "Str10a");
change(userData, "STRING", space.getAddress(20), "Str20a"); change(userData, "STRING", space.getAddress(20), "Str20a");
assertFalse(program.isChanged()); assertFalse(program.isChanged());
}
finally {
program.release(this);
}
program = program.release(this);
(Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
try {
assertFalse(program.isChanged());
// Verify user data content program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
ProgramUserData userData = program.getProgramUserData(); assertFalse(program.isChanged());
StringPropertyMap map =
userData.getStringProperty(testName.getMethodName(), "STRING", false); // Verify user data content
assertEquals("Str0", map.getString(space.getAddress(0))); userData = program.getProgramUserData();
assertEquals("Str10a", map.getString(space.getAddress(10))); map = userData.getStringProperty(testName.getMethodName(), "STRING", false);
assertEquals("Str20a", map.getString(space.getAddress(20))); assertEquals("Str0", map.getString(space.getAddress(0)));
assertFalse(program.isChanged()); assertEquals("Str10a", map.getString(space.getAddress(10)));
} assertEquals("Str20a", map.getString(space.getAddress(20)));
finally { assertFalse(program.isChanged());
program.release(this);
} program.release(this);
assertEquals("User data files missing", 2, userDataSubDir.list().length); assertEquals("User data files missing", 2, userDataSubDir.list().length);
df.delete(); df.delete();
@ -288,18 +265,14 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest {
// TODO: Multi-user repository connect case not tested // TODO: Multi-user repository connect case not tested
Program program = Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
(Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
space = program.getAddressFactory().getDefaultAddressSpace(); space = program.getAddressFactory().getDefaultAddressSpace();
try { // Create user data content
// Create user data content ProgramUserData userData = program.getProgramUserData();
ProgramUserData userData = program.getProgramUserData(); change(userData, "STRING", space.getAddress(0), "Str0");
change(userData, "STRING", space.getAddress(0), "Str0"); change(userData, "STRING", space.getAddress(10), "Str10");
change(userData, "STRING", space.getAddress(10), "Str10");
} program.release(this);
finally {
program.release(this);
}
// Close and re-open (domain file remains intact) // Close and re-open (domain file remains intact)
project.close(); project.close();
@ -308,23 +281,19 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest {
df = project.getProjectData().getFile("/test"); df = project.getProjectData().getFile("/test");
program = program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
(Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY); assertFalse(program.isChanged());
try {
assertFalse(program.isChanged());
// Verify user data content // Verify user data content
ProgramUserData userData = program.getProgramUserData(); userData = program.getProgramUserData();
StringPropertyMap map = StringPropertyMap map =
userData.getStringProperty(testName.getMethodName(), "STRING", false); userData.getStringProperty(testName.getMethodName(), "STRING", false);
assertEquals("Str0", map.getString(space.getAddress(0))); assertEquals("Str0", map.getString(space.getAddress(0)));
assertEquals("Str10", map.getString(space.getAddress(10))); assertEquals("Str10", map.getString(space.getAddress(10)));
assertNull(map.getString(space.getAddress(20))); assertNull(map.getString(space.getAddress(20)));
assertFalse(program.isChanged()); assertFalse(program.isChanged());
}
finally { program.release(this);
program.release(this);
}
// Close and re-open (domain file removed) // Close and re-open (domain file removed)
project.close(); project.close();
@ -351,4 +320,70 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest {
} }
} }
@Test
public void testSetGetStringProperty() throws Exception {
Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
ProgramUserData userData = program.getProgramUserData();
userData.setStringProperty("Foo", "Bar");
assertEquals("Bar", userData.getStringProperty("Foo", null));
program.release(this);
program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
userData = program.getProgramUserData();
assertEquals("Bar", userData.getStringProperty("Foo", null));
program.release(this);
}
@Test
public void testGetStringPropertyNames() throws Exception {
Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
ProgramUserData userData = program.getProgramUserData();
userData.setStringProperty("Alpha", "alpha");
userData.setStringProperty("Beta", "beta");
Set<String> stringPropertyNames = userData.getStringPropertyNames();
assertEquals(2, stringPropertyNames.size());
assertTrue(stringPropertyNames.contains("Alpha"));
assertTrue(stringPropertyNames.contains("Beta"));
program.release(this);
}
@Test
public void testGetStringPropertyForNondefinedProperty() throws Exception {
Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
ProgramUserData userData = program.getProgramUserData();
assertEquals("xxx", userData.getStringProperty("ABC", "xxx"));
assertEquals("zzz", userData.getStringProperty("ABC", "zzz"));
program.release(this);
}
@Test
public void testRemoveStringProperty() throws Exception {
Program program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
ProgramUserData userData = program.getProgramUserData();
userData.setStringProperty("foo", "bar");
program.release(this);
// reload and make sure the property is there
program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
userData = program.getProgramUserData();
assertEquals("bar", userData.getStringProperty("foo", null));
assertEquals("bar", userData.removeStringProperty("foo"));
program.release(this);
// reload and make sure the property is gone
program = (Program) df.getDomainObject(this, false, false, TaskMonitor.DUMMY);
userData = program.getProgramUserData();
assertNull(userData.getStringProperty("foo", null));
program.release(this);
}
} }

View file

@ -602,6 +602,7 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
DomainObjectAdapterDB userData = getUserData(); DomainObjectAdapterDB userData = getUserData();
if (userData != null && userData.isChanged() && (getDomainFile() instanceof GhidraFile)) { if (userData != null && userData.isChanged() && (getDomainFile() instanceof GhidraFile)) {
try { try {
userData.prepareToSave();
userData.save(null, TaskMonitorAdapter.DUMMY_MONITOR); userData.save(null, TaskMonitorAdapter.DUMMY_MONITOR);
} }
catch (CancelledException e) { catch (CancelledException e) {

View file

@ -242,8 +242,9 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData
Language newLanguage = language; Language newLanguage = language;
Language oldLanguage = OldLanguageFactory.getOldLanguageFactory().getOldLanguage( Language oldLanguage = OldLanguageFactory.getOldLanguageFactory()
languageID, languageVersion); .getOldLanguage(
languageID, languageVersion);
if (oldLanguage == null) { if (oldLanguage == null) {
// Assume minor version behavior - old language does not exist for current major version // Assume minor version behavior - old language does not exist for current major version
Msg.error(this, "Old language specification not found: " + languageID + Msg.error(this, "Old language specification not found: " + languageID +
@ -253,8 +254,9 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData
// Ensure that we can upgrade the language // Ensure that we can upgrade the language
languageUpgradeTranslator = languageUpgradeTranslator =
LanguageTranslatorFactory.getLanguageTranslatorFactory().getLanguageTranslator( LanguageTranslatorFactory.getLanguageTranslatorFactory()
oldLanguage, newLanguage); .getLanguageTranslator(
oldLanguage, newLanguage);
if (languageUpgradeTranslator == null) { if (languageUpgradeTranslator == null) {
throw new LanguageNotFoundException(language.getLanguageID(), throw new LanguageNotFoundException(language.getLanguageID(),
"(Ver " + languageVersion + ".x" + " -> " + newLanguage.getVersion() + "." + "(Ver " + languageVersion + ".x" + " -> " + newLanguage.getVersion() + "." +
@ -285,8 +287,9 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData
throws LanguageNotFoundException { throws LanguageNotFoundException {
languageUpgradeTranslator = languageUpgradeTranslator =
LanguageTranslatorFactory.getLanguageTranslatorFactory().getLanguageTranslator( LanguageTranslatorFactory.getLanguageTranslatorFactory()
languageID, languageVersion); .getLanguageTranslator(
languageID, languageVersion);
if (languageUpgradeTranslator == null) { if (languageUpgradeTranslator == null) {
throw e; throw e;
} }
@ -372,6 +375,7 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData
if (storedVersion < UPGRADE_REQUIRED_BEFORE_VERSION) { if (storedVersion < UPGRADE_REQUIRED_BEFORE_VERSION) {
requiresUpgrade = true; requiresUpgrade = true;
} }
loadMetadata();
return requiresUpgrade ? new VersionException(true) : null; return requiresUpgrade ? new VersionException(true) : null;
} }
@ -686,4 +690,27 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData
// fireEvent(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_SAVED)); // fireEvent(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_SAVED));
} }
@Override
public void setStringProperty(String propertyName, String value) {
metadata.put(propertyName, value);
changed = true;
}
@Override
public String getStringProperty(String propertyName, String defaultValue) {
String value = metadata.get(propertyName);
return value == null ? defaultValue : value;
}
@Override
public Set<String> getStringPropertyNames() {
return metadata.keySet();
}
@Override
public String removeStringProperty(String propertyName) {
changed = true;
return metadata.remove(propertyName);
}
} }

View file

@ -16,6 +16,7 @@
package ghidra.program.model.listing; package ghidra.program.model.listing;
import java.util.List; import java.util.List;
import java.util.Set;
import ghidra.framework.model.UserData; import ghidra.framework.model.UserData;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
@ -33,16 +34,16 @@ public interface ProgramUserData extends UserData {
/** /**
* End a previously started transaction * End a previously started transaction
* @param transactionID * @param transactionID the id of the transaction to close
*/ */
public void endTransaction(int transactionID); public void endTransaction(int transactionID);
/** /**
* Get a address-based String property map * Get a address-based String property map
* @param owner name of property owner (e.g., plugin name) * @param owner name of property owner (e.g., plugin name)
* @param propertyName * @param propertyName the name of property map
* @param create creates the property map if it does not exist * @param create creates the property map if it does not exist
* @return property map * @return the property map for the given name
* @throws PropertyTypeMismatchException if a conflicting map definition was found * @throws PropertyTypeMismatchException if a conflicting map definition was found
*/ */
public StringPropertyMap getStringProperty(String owner, String propertyName, boolean create) public StringPropertyMap getStringProperty(String owner, String propertyName, boolean create)
@ -51,7 +52,7 @@ public interface ProgramUserData extends UserData {
/** /**
* Get a address-based Long property map * Get a address-based Long property map
* @param owner name of property owner (e.g., plugin name) * @param owner name of property owner (e.g., plugin name)
* @param propertyName * @param propertyName the name of property map
* @param create creates the property map if it does not exist * @param create creates the property map if it does not exist
* @return property map * @return property map
* @throws PropertyTypeMismatchException if a conflicting map definition was found * @throws PropertyTypeMismatchException if a conflicting map definition was found
@ -62,7 +63,7 @@ public interface ProgramUserData extends UserData {
/** /**
* Get a address-based Integer property map * Get a address-based Integer property map
* @param owner name of property owner (e.g., plugin name) * @param owner name of property owner (e.g., plugin name)
* @param propertyName * @param propertyName the name of property map
* @param create creates the property map if it does not exist * @param create creates the property map if it does not exist
* @return property map * @return property map
* @throws PropertyTypeMismatchException if a conflicting map definition was found * @throws PropertyTypeMismatchException if a conflicting map definition was found
@ -73,7 +74,7 @@ public interface ProgramUserData extends UserData {
/** /**
* Get a address-based Boolean property map * Get a address-based Boolean property map
* @param owner name of property owner (e.g., plugin name) * @param owner name of property owner (e.g., plugin name)
* @param propertyName * @param propertyName the name of property map
* @param create creates the property map if it does not exist * @param create creates the property map if it does not exist
* @return property map * @return property map
* @throws PropertyTypeMismatchException if a conflicting map definition was found * @throws PropertyTypeMismatchException if a conflicting map definition was found
@ -84,7 +85,8 @@ public interface ProgramUserData extends UserData {
/** /**
* Get a address-based Saveable-object property map * Get a address-based Saveable-object property map
* @param owner name of property owner (e.g., plugin name) * @param owner name of property owner (e.g., plugin name)
* @param propertyName * @param propertyName the name of property map
* @param saveableObjectClass the class type for the object property map
* @param create creates the property map if it does not exist * @param create creates the property map if it does not exist
* @return property map * @return property map
* @throws PropertyTypeMismatchException if a conflicting map definition was found * @throws PropertyTypeMismatchException if a conflicting map definition was found
@ -101,19 +103,50 @@ public interface ProgramUserData extends UserData {
/** /**
* Returns list of all property owners for which property maps have been defined. * Returns list of all property owners for which property maps have been defined.
* @return list of all property owners for which property maps have been defined.
*/ */
public List<String> getPropertyOwners(); public List<String> getPropertyOwners();
/** /**
* Returns all properties lists contained by this domain object. * Returns all names of all the Options objects store in the user data
* *
* @return all property lists contained by this domain object. * @return all names of all the Options objects store in the user data
*/ */
public List<String> getOptionsNames(); public List<String> getOptionsNames();
/** /**
* Get the property list for the given name. * Get the Options for the given optionsName
* @param propertyListName name of property list * @param optionsName the name of the options options to retrieve
* @return The options for the given name
*/ */
public Options getOptions(String propertyListName); public Options getOptions(String optionsName);
/**
* Sets the given String property
* @param propertyName the name of the property
* @param value the value of the property
*/
public void setStringProperty(String propertyName, String value);
/**
* Gets the value for the given property name
* @param propertyName the name of the string property to retrieve
* @param defaultValue the value to return if there is no saved value for the given name
* @return the value for the given property name
*/
public String getStringProperty(String propertyName, String defaultValue);
/**
* Removes the String property with the given name;
* @param propertyName the name of the property to remove;
* @return returns the value of the property that was removed or null if the property doesn't
* exist
*/
public String removeStringProperty(String propertyName);
/**
* Returns a set of all String properties that have been set on this ProgramUserData object
* @return a set of all String properties that have been set on this ProgramUserData object
*/
public Set<String> getStringPropertyNames();
} }