Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 052e463

Browse files
committedFeb 11, 2020
Merge remote-tracking branch 'origin/GT-3535_ghidravore_category_performance'
2 parents ea45a04 + 4ce5645 commit 052e463

File tree

5 files changed

+228
-39
lines changed

5 files changed

+228
-39
lines changed
 

‎Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/data/CategoryTest.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,37 @@ public void testDataTypeDeleted() throws Exception {
575575
assertEquals(comps.length - 1, newDt.getDefinedComponents().length);
576576
}
577577

578+
@Test
579+
public void testDataTypeConflictHandling() throws Exception {
580+
Category sub1 = root.createCategory("Cat1");
581+
DataType dt1 = new StructureDataType("DT", 1);
582+
DataType dt2 = new StructureDataType("DT", 2);
583+
DataType added1 = sub1.addDataType(dt1, null);
584+
DataType added2 = sub1.addDataType(dt2, null);
585+
assertEquals("DT", added1.getName());
586+
assertEquals("DT.conflict", added2.getName());
587+
588+
List<DataType> list = sub1.getDataTypesByBaseName("DT");
589+
assertEquals(2, list.size());
590+
assertEquals(added1, list.get(0));
591+
assertEquals(added2, list.get(1));
592+
593+
list = sub1.getDataTypesByBaseName("DT.conflict");
594+
assertEquals(2, list.size());
595+
assertEquals(added1, list.get(0));
596+
assertEquals(added2, list.get(1));
597+
598+
sub1.remove(added2, TaskMonitor.DUMMY);
599+
list = sub1.getDataTypesByBaseName("DT");
600+
assertEquals(1, list.size());
601+
assertEquals(added1, list.get(0));
602+
603+
list = sub1.getDataTypesByBaseName("DT.conflict");
604+
assertEquals(1, list.size());
605+
assertEquals(added1, list.get(0));
606+
607+
}
608+
578609
@Test
579610
public void testGetDataTypeManager() throws Exception {
580611
Category sub1 = root.createCategory("SubCat-A");
@@ -772,10 +803,7 @@ public void testListenerDataTypeChanged() throws Exception {
772803
clearEvents();
773804

774805
struct2 = (Structure) newDt.insert(3, struct2).getDataType();
775-
int eventCount = getEventCount();
776-
if (4 != eventCount) {
777-
System.err.println("halt!");
778-
}
806+
779807
assertEquals(5, getEventCount());
780808
Event ev = getEvent(4);
781809
assertEquals("DT Changed", ev.evName);

‎Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/CategoryDB.java

Lines changed: 152 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
package ghidra.program.database.data;
1717

1818
import java.io.IOException;
19-
import java.util.List;
20-
import java.util.Map;
19+
import java.util.*;
2120
import java.util.concurrent.ConcurrentHashMap;
2221

2322
import db.Record;
@@ -26,6 +25,7 @@
2625
import ghidra.program.model.data.*;
2726
import ghidra.program.model.data.DataTypeConflictHandler.ConflictResult;
2827
import ghidra.util.InvalidNameException;
28+
import ghidra.util.Lock;
2929
import ghidra.util.exception.AssertException;
3030
import ghidra.util.exception.DuplicateNameException;
3131
import ghidra.util.task.TaskMonitor;
@@ -41,6 +41,7 @@ class CategoryDB extends DatabaseObject implements Category {
4141

4242
private LazyLoadingCachingMap<String, CategoryDB> subcategoryMap;
4343
private LazyLoadingCachingMap<String, DataType> dataTypeMap;
44+
private ConflictMap conflictMap;
4445

4546
/**
4647
* Category Constructor
@@ -57,19 +58,19 @@ class CategoryDB extends DatabaseObject implements Category {
5758
this.name = name;
5859
this.parent = parent;
5960

60-
subcategoryMap = new LazyLoadingCachingMap<>(mgr.lock, CategoryDB.class) {
61+
subcategoryMap = new LazyLoadingCachingMap<>(mgr.lock) {
6162
@Override
6263
public Map<String, CategoryDB> loadMap() {
6364
return buildSubcategoryMap();
6465
}
6566
};
66-
dataTypeMap = new LazyLoadingCachingMap<>(mgr.lock, DataType.class) {
67+
dataTypeMap = new LazyLoadingCachingMap<>(mgr.lock) {
6768
@Override
6869
public Map<String, DataType> loadMap() {
6970
return createDataTypeMap();
7071
}
7172
};
72-
73+
conflictMap = new ConflictMap(mgr.lock);
7374
}
7475

7576
/**
@@ -102,6 +103,7 @@ protected boolean refresh() {
102103
protected boolean refresh(Record rec) {
103104
subcategoryMap.clear();
104105
dataTypeMap.clear();
106+
conflictMap.clear();
105107

106108
if (isRoot()) {
107109
return true;
@@ -210,13 +212,26 @@ private Map<String, DataType> createDataTypeMap() {
210212
return map;
211213
}
212214

215+
private String getBaseName(String dataTypeName) {
216+
int indexOf = dataTypeName.indexOf(DataType.CONFLICT_SUFFIX);
217+
if (indexOf <= 0) {
218+
return dataTypeName;
219+
}
220+
return dataTypeName.substring(0, indexOf);
221+
}
222+
223+
private boolean isConflictName(String dataTypeName) {
224+
return dataTypeName.contains(DataType.CONFLICT_SUFFIX);
225+
}
226+
213227
/**
214228
* @see ghidra.program.model.data.Category#getCategories()
215229
*/
216230
@Override
217231
public Category[] getCategories() {
218232
validate(mgr.lock);
219-
return subcategoryMap.valuesToArray();
233+
Collection<CategoryDB> categories = subcategoryMap.values();
234+
return categories.toArray(new Category[categories.size()]);
220235
}
221236

222237
/**
@@ -225,7 +240,8 @@ public Category[] getCategories() {
225240
@Override
226241
public DataType[] getDataTypes() {
227242
validate(mgr.lock);
228-
return dataTypeMap.valuesToArray();
243+
Collection<DataType> dataTypes = dataTypeMap.values();
244+
return dataTypes.toArray(new DataType[dataTypes.size()]);
229245
}
230246

231247
/**
@@ -587,19 +603,144 @@ private void catagoryRenamed(CategoryDB childCategory, String oldName) {
587603
}
588604

589605
void dataTypeRenamed(DataType childDataType, String oldName) {
590-
dataTypeMap.remove(oldName);
591-
dataTypeMap.put(childDataType.getName(), childDataType);
606+
dataTypeRemoved(oldName);
607+
dataTypeAdded(childDataType);
592608
}
593609

594-
void dataTypeAdded(DataType childDataType) {
595-
dataTypeMap.put(childDataType.getName(), childDataType);
610+
void dataTypeAdded(DataType dataType) {
611+
String dtName = dataType.getName();
612+
dataTypeMap.put(dtName, dataType);
613+
if (isConflictName(dtName)) {
614+
conflictMap.addDataType(dataType);
615+
}
596616
}
597617

598618
void dataTypeRemoved(String dataTypeName) {
599619
dataTypeMap.remove(dataTypeName);
620+
if (isConflictName(dataTypeName)) {
621+
conflictMap.removeDataTypeName(dataTypeName);
622+
}
600623
}
601624

602625
void categoryAdded(CategoryDB cat) {
603626
subcategoryMap.put(cat.getName(), cat);
604627
}
628+
629+
@Override
630+
public List<DataType> getDataTypesByBaseName(String dataTypeName) {
631+
List<DataType> list = new ArrayList<>();
632+
String baseName = getBaseName(dataTypeName);
633+
634+
DataType baseType = dataTypeMap.get(baseName);
635+
if (baseType != null) {
636+
list.add(baseType);
637+
}
638+
639+
List<DataType> relatedNameDataTypes = conflictMap.getDataTypesByBaseName(baseName);
640+
list.addAll(relatedNameDataTypes);
641+
return list;
642+
}
643+
644+
/**
645+
* Class to handle the complexities of having a map as the value in a LazyLoadingCachingMap
646+
* This map uses the data type's base name as the key (i.e. all .conflict suffixes stripped off.)
647+
* The value is another map that maps the actual data type's name to the data type. This map
648+
* effectively provides an efficient way to get all data types in a category that have the
649+
* same name, but possibly have had their name modified (by appending .conflict) to get around
650+
* the requirement that names have to be unique in the same category.
651+
*/
652+
private class ConflictMap extends LazyLoadingCachingMap<String, Map<String, DataType>> {
653+
654+
ConflictMap(Lock lock) {
655+
super(lock);
656+
}
657+
658+
/**
659+
* Creates a map of all data types whose name has a .conflict suffix where the key
660+
* is the base name and {@link LazyLoadingCachingMap} the value is a map of actual name
661+
* to data type. This mapping is
662+
* maintained as a lazy cache map. This is only called by the super class when the
663+
* cached needs to be populated and we are depending on it to acquire the necessary
664+
* database lock. (See {@link LazyLoadingCachingMap#loadMap()}
665+
* @return the loaded map
666+
*/
667+
@Override
668+
protected Map<String, Map<String, DataType>> loadMap() {
669+
Map<String, Map<String, DataType>> map = new HashMap<>();
670+
Collection<DataType> values = dataTypeMap.values();
671+
for (DataType dataType : values) {
672+
String dataTypeName = dataType.getName();
673+
if (isConflictName(dataTypeName)) {
674+
String baseName = getBaseName(dataTypeName);
675+
Map<String, DataType> innerMap =
676+
map.computeIfAbsent(baseName, b -> new HashMap<>());
677+
innerMap.put(dataTypeName, dataType);
678+
}
679+
}
680+
return map;
681+
}
682+
683+
/**
684+
* Adds the data type to the conflict mapping structure. If the mapping is currently not
685+
* loaded then this method can safely do nothing. This method is synchronized to provide
686+
* thread safe access/manipulation of the map.
687+
* @param dataType the data type to add to the mapping if the mapping is already loaded
688+
*/
689+
synchronized void addDataType(DataType dataType) {
690+
// if the cache is not currently populated, don't need to do anything
691+
Map<String, Map<String, DataType>> map = getMap();
692+
if (map == null) {
693+
return;
694+
}
695+
696+
String dataTypeName = dataType.getName();
697+
String baseName = getBaseName(dataTypeName);
698+
Map<String, DataType> innerMap = map.computeIfAbsent(baseName, b -> new HashMap<>());
699+
innerMap.put(dataTypeName, dataType);
700+
}
701+
702+
/**
703+
* Removes the data type with the given name from the conflict mapping structure. If the
704+
* mapping is currently not loaded then this method can safely do nothing. This method is
705+
* synchronized to provide thread safe access/manipulate of the map.
706+
* @param dataTypeName the name of the data type to remove from this mapping
707+
*/
708+
synchronized void removeDataTypeName(String dataTypeName) {
709+
Map<String, Map<String, DataType>> map = getMap();
710+
if (map == null) {
711+
return;
712+
}
713+
String baseName = getBaseName(dataTypeName);
714+
Map<String, DataType> innerMap = map.get(baseName);
715+
if (innerMap == null) {
716+
return;
717+
}
718+
innerMap.remove(dataTypeName);
719+
}
720+
721+
/**
722+
* Returns a list of all data types that have conflict names for the given base name
723+
* @param baseName the data type base name to search for (i.e. the .conflict suffix removed)
724+
* @return a list of all conflict named data types that would have the given base name if
725+
* no conflicts existed
726+
*/
727+
List<DataType> getDataTypesByBaseName(String baseName) {
728+
729+
// Note that the following call to get MUST NOT be in a synchronized block because
730+
// it may trigger a loading of the cache which requires a database lock and you
731+
// can't be synchronized on this class when acquiring a database lock or else a
732+
// deadlock will occur.
733+
Map<String, DataType> map = get(baseName);
734+
if (map == null) {
735+
return Collections.emptyList();
736+
}
737+
738+
// the following must be synchronized so that the implied iterator can complete without
739+
// another thread changing the map's values.
740+
synchronized (this) {
741+
return new ArrayList<>(map.values());
742+
}
743+
}
744+
745+
}
605746
}

‎Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -934,14 +934,12 @@ private DataType findEquivalentDataTypeSameLocation(DataType dataType, String ex
934934
if (category == null) {
935935
return null;
936936
}
937-
String namePrefix = dtName + DataType.CONFLICT_SUFFIX;
938-
DataType[] dataTypes = category.getDataTypes();
939-
for (DataType candidate : dataTypes) {
937+
List<DataType> relatedByName = category.getDataTypesByBaseName(dtName);
938+
939+
for (DataType candidate : relatedByName) {
940940
String candidateName = candidate.getName();
941-
if (candidateName.startsWith(namePrefix)) {
942-
if (!candidateName.equals(excludedName) && candidate.isEquivalent(dataType)) {
943-
return candidate;
944-
}
941+
if (!candidateName.equals(excludedName) && candidate.isEquivalent(dataType)) {
942+
return candidate;
945943
}
946944
}
947945
return null;
@@ -3208,13 +3206,12 @@ List<DataType> getParentDataTypes(long childID) {
32083206
lock.acquire();
32093207
try {
32103208
long[] ids = parentChildAdapter.getParentIds(childID);
3211-
// TODO: consider deduping ids using Set
32123209
List<DataType> dts = new ArrayList<>();
3213-
for (int i = 0; i < ids.length; i++) {
3214-
DataType dt = getDataType(ids[i]);
3210+
for (long id : ids) {
3211+
DataType dt = getDataType(id);
32153212
if (dt == null) {
32163213
// cleanup invalid records for missing parent
3217-
attemptRecordRemovalForParent(ids[i]);
3214+
attemptRecordRemovalForParent(id);
32183215
}
32193216
else {
32203217
dts.add(dt);

‎Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/LazyLoadingCachingMap.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
package ghidra.program.database.data;
1717

1818
import java.lang.ref.SoftReference;
19-
import java.lang.reflect.Array;
20-
import java.util.Map;
19+
import java.util.*;
2120

2221
import ghidra.util.Lock;
2322

@@ -39,15 +38,14 @@ public abstract class LazyLoadingCachingMap<K, V> {
3938

4039
private Lock lock;
4140
private SoftReference<Map<K, V>> softRef;
42-
private Class<V> valueClass;
4341

44-
protected LazyLoadingCachingMap(Lock lock, Class<V> valueClass) {
42+
protected LazyLoadingCachingMap(Lock lock) {
4543
this.lock = lock;
46-
this.valueClass = valueClass;
4744
}
4845

4946
/**
50-
* This method will reload the map data from scratch.
47+
* This method will reload the map data from scratch. Subclass may assume that the database
48+
* lock has been acquired.
5149
* @return a map containing all current key, value pairs.
5250
*/
5351
protected abstract Map<K, V> loadMap();
@@ -96,13 +94,13 @@ public V get(K key) {
9694
}
9795
}
9896

99-
public V[] valuesToArray() {
97+
/**
98+
* Returns an unmodifiable view of the values in this map.
99+
* @return an unmodifiable view of the values in this map.
100+
*/
101+
public Collection<V> values() {
100102
Map<K, V> map = getOrLoadMap();
101-
synchronized (this) {
102-
@SuppressWarnings("unchecked")
103-
V[] array = (V[]) Array.newInstance(valueClass, map.size());
104-
return map.values().toArray(array);
105-
}
103+
return Collections.unmodifiableCollection(map.values());
106104
}
107105

108106
private Map<K, V> getOrLoadMap() {
@@ -113,6 +111,15 @@ private Map<K, V> getOrLoadMap() {
113111
return map;
114112
}
115113
}
114+
115+
// We must get the database lock before calling loadMap(). Also, we can't get the
116+
// database lock while having the synchronization lock for this class or a deadlock can
117+
// occur, since the other methods may be called while the client has the db lock.
118+
// Note: all other places where the map is being used or manipulated, it must be done
119+
// while having the class's synchronization lock since the map itself is not thread safe.
120+
// It should be safe here since it creates a new map and then in one operation it sets it
121+
// as the map to be used elsewhere.
122+
116123
lock.acquire();
117124
try {
118125
map = getMap();
@@ -132,11 +139,12 @@ private Map<K, V> getOrLoadMap() {
132139
* "lock".
133140
* @return the underlying map of key,value pairs or null if it is currently not loaded.
134141
*/
135-
private Map<K, V> getMap() {
142+
protected Map<K, V> getMap() {
136143
if (softRef == null) {
137144
return null;
138145
}
139146
return softRef.get();
140147
}
141148

149+
142150
}

‎Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/Category.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package ghidra.program.model.data;
1717

18+
import java.util.List;
19+
1820
import ghidra.util.InvalidNameException;
1921
import ghidra.util.exception.DuplicateNameException;
2022
import ghidra.util.task.TaskMonitor;
@@ -48,6 +50,19 @@ public interface Category extends Comparable<Category> {
4850
*/
4951
public abstract DataType[] getDataTypes();
5052

53+
/**
54+
* Get all data types in this category whose base name matches the base name of the given name.
55+
* The base name of a name is the first part of the string up to where the first ".conflict"
56+
* occurs. In other words, finds all data types whose name matches the given name once
57+
* any conflict suffixes have been removed from both the given name and the data types
58+
* that are being scanned.
59+
* @param name the name for which to get conflict related data types in this category. Note: the
60+
* name that is passed in will be normalized to its base name, so you may pass in names with .conflict
61+
* appended as a convenience.
62+
* @return a list of data types that have the same base name as the base name of the given name
63+
*/
64+
public abstract List<DataType> getDataTypesByBaseName(String name);
65+
5166
/**
5267
* Adds the given datatype to this category.
5368
* @param dt the datatype to add to this category.

0 commit comments

Comments
 (0)
Please sign in to comment.