GP-6691 - Data Type Finder - Updated search results to include the field name if one is available. Added a new table column to show just the field name.

This commit is contained in:
dragonmacher
2026-05-01 17:39:14 -04:00
parent 7db1b83ff5
commit 0999e6b9a4
12 changed files with 109 additions and 97 deletions
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,6 +16,7 @@
package ghidra.app.plugin.core.navigation.locationreferences; package ghidra.app.plugin.core.navigation.locationreferences;
import ghidra.program.database.symbol.FunctionSymbol; import ghidra.program.database.symbol.FunctionSymbol;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
@@ -67,7 +68,14 @@ public class FunctionDefinitionLocationDescriptor extends GenericDataTypeLocatio
continue; continue;
} }
accumulator.add(new LocationReference(symbol.getAddress())); ProgramLocation location = symbol.getProgramLocation();
Address address = symbol.getAddress();
if (location != null) {
accumulator.add(new LocationReference(address, null, location));
}
else {
accumulator.add(new LocationReference(address));
}
} }
} }
@@ -38,6 +38,11 @@ public class LocationReference implements Comparable<LocationReference> {
private final SearchLocationContext context; private final SearchLocationContext context;
private final ProgramLocation location; private final ProgramLocation location;
/**
* Optional field name.
*/
private String fieldName;
/** /**
* Optional reference object. Some clients do not have actual references. * Optional reference object. Some clients do not have actual references.
*/ */
@@ -67,20 +72,25 @@ public class LocationReference implements Comparable<LocationReference> {
this.reference = reference; this.reference = reference;
} }
LocationReference(Address locationOfUseAddress, String refType, boolean isOffcutReference) { LocationReference(Reference reference, boolean isOffcutReference, String fieldName) {
this(locationOfUseAddress, null, refType, EMPTY_CONTEXT, isOffcutReference); this(reference.getFromAddress(), null, getRefType(reference), EMPTY_CONTEXT,
isOffcutReference);
this.reference = reference;
this.fieldName = fieldName;
} }
LocationReference(Address locationOfUseAddress) { LocationReference(Address locationOfUseAddress) {
this(locationOfUseAddress, null, null, EMPTY_CONTEXT, false); this(locationOfUseAddress, null, null, EMPTY_CONTEXT, false);
} }
LocationReference(Address locationOfUseAddress, String context) { LocationReference(Address locationOfUseAddress, SearchLocationContext context) {
this(locationOfUseAddress, null, null, SearchLocationContext.get(context), false); this(locationOfUseAddress, null, null, SearchLocationContext.get(context), false);
} }
LocationReference(Address locationOfUseAddress, SearchLocationContext context) { LocationReference(Address locationOfUseAddress, SearchLocationContext context,
String fieldName) {
this(locationOfUseAddress, null, null, SearchLocationContext.get(context), false); this(locationOfUseAddress, null, null, SearchLocationContext.get(context), false);
this.fieldName = fieldName;
} }
LocationReference(Address locationOfUseAddress, String context, ProgramLocation location) { LocationReference(Address locationOfUseAddress, String context, ProgramLocation location) {
@@ -150,6 +160,14 @@ public class LocationReference implements Comparable<LocationReference> {
return reference; return reference;
} }
/**
* Returns the field name for this location reference or null if there is no field name.
* @return the field name; may be null
*/
public String getFieldName() {
return fieldName;
}
/** /**
* Returns true if this class has a {@link Reference} and that reference is not dynamic (i.e., * Returns true if this class has a {@link Reference} and that reference is not dynamic (i.e.,
* the reference exists in the database). * the reference exists in the database).
@@ -23,6 +23,7 @@ import org.apache.commons.lang3.StringUtils;
import docking.widgets.search.SearchLocationContext; import docking.widgets.search.SearchLocationContext;
import docking.widgets.search.SearchLocationContextRenderer; import docking.widgets.search.SearchLocationContextRenderer;
import docking.widgets.table.GTableCellRenderingData; import docking.widgets.table.GTableCellRenderingData;
import docking.widgets.table.TableColumnDescriptor;
import ghidra.docking.settings.Settings; import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
@@ -57,8 +58,14 @@ class LocationReferencesTableModel extends AddressBasedTableModel<LocationRefere
super("References", locationReferencesProvider.getTool(), super("References", locationReferencesProvider.getTool(),
locationReferencesProvider.getProgram(), null, true); locationReferencesProvider.getProgram(), null, true);
this.provider = locationReferencesProvider; this.provider = locationReferencesProvider;
}
addTableColumn(new ContextTableColumn()); @Override
protected TableColumnDescriptor<LocationReference> createTableColumnDescriptor() {
TableColumnDescriptor<LocationReference> descriptor = super.createTableColumnDescriptor();
descriptor.addVisibleColumn(new ContextColumn());
descriptor.addHiddenColumn(new FieldNameColumn());
return descriptor;
} }
@Override @Override
@@ -128,7 +135,7 @@ class LocationReferencesTableModel extends AddressBasedTableModel<LocationRefere
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
private class ContextTableColumn private class ContextColumn
extends AbstractProgramBasedDynamicTableColumn<LocationReference, LocationReference> { extends AbstractProgramBasedDynamicTableColumn<LocationReference, LocationReference> {
private static final String OFFCUT_STRING = "<< OFFCUT >>"; private static final String OFFCUT_STRING = "<< OFFCUT >>";
@@ -262,7 +269,7 @@ class LocationReferencesTableModel extends AddressBasedTableModel<LocationRefere
} }
SearchLocationContext context = rowObject.getContext(); SearchLocationContext context = rowObject.getContext();
return contextRenderer.renderHtmlContext(data, context); return contextRenderer.renderHtmlContext(data, context, true);
} }
@Override @Override
@@ -279,4 +286,19 @@ class LocationReferencesTableModel extends AddressBasedTableModel<LocationRefere
} }
private class FieldNameColumn
extends AbstractProgramBasedDynamicTableColumn<LocationReference, String> {
@Override
public String getColumnName() {
return "Field Name";
}
@Override
public String getValue(LocationReference rowObject, Settings settings,
Program data, ServiceProvider sp) throws IllegalArgumentException {
return rowObject.getFieldName();
}
}
} }
@@ -330,7 +330,9 @@ public final class ReferenceUtils {
Consumer<DataTypeReference> callback = ref -> { Consumer<DataTypeReference> callback = ref -> {
SearchLocationContext context = ref.getContext(); SearchLocationContext context = ref.getContext();
LocationReference locationReference = new LocationReference(ref.getAddress(), context); String fieldName = ref.getFieldName();
LocationReference locationReference =
new LocationReference(ref.getAddress(), context, fieldName);
accumulator.add(locationReference); accumulator.add(locationReference);
}; };
@@ -1039,9 +1041,11 @@ public final class ReferenceUtils {
private static LocationReference createReferenceFromDefinedData(Data data, private static LocationReference createReferenceFromDefinedData(Data data,
FieldMatcher fieldMatcher) { FieldMatcher fieldMatcher) {
Address dataAddress = data.getMinAddress(); Address dataAddress = data.getMinAddress();
String pathName = data.getPathName();
if (fieldMatcher.isIgnored()) { if (fieldMatcher.isIgnored()) {
// no field to match; include the hit // no field to match; include the hit
return new LocationReference(dataAddress, data.getPathName()); SearchLocationContext context = SearchLocationContext.get(pathName);
return new LocationReference(dataAddress, context);
} }
DataType dt = data.getDataType(); DataType dt = data.getDataType();
@@ -1052,8 +1056,11 @@ public final class ReferenceUtils {
} }
DataType baseDt = getBaseDataType(data.getDataType()); DataType baseDt = getBaseDataType(data.getDataType());
String fieldName = fieldMatcher.getFieldName();
if (matchesEnumField(data, baseDt, fieldMatcher)) { if (matchesEnumField(data, baseDt, fieldMatcher)) {
return new LocationReference(dataAddress, fieldMatcher.getDisplayText()); String text = fieldMatcher.getDisplayText();
SearchLocationContext context = SearchLocationContext.get(text);
return new LocationReference(dataAddress, context, fieldName);
} }
DataTypeComponent component = getDataTypeComponent(baseDt, fieldMatcher); DataTypeComponent component = getDataTypeComponent(baseDt, fieldMatcher);
@@ -1064,13 +1071,14 @@ public final class ReferenceUtils {
Address componentAddress; Address componentAddress;
try { try {
componentAddress = dataAddress.addNoWrap(component.getOffset()); componentAddress = dataAddress.addNoWrap(component.getOffset());
return new LocationReference(componentAddress, String text = pathName + "." + fieldName;
data.getPathName() + "." + fieldMatcher.getFieldName()); SearchLocationContext context = SearchLocationContext.get(text);
return new LocationReference(componentAddress, context, fieldName);
} }
catch (AddressOverflowException e) { catch (AddressOverflowException e) {
// shouldn't happen // shouldn't happen
Msg.error(ReferenceUtils.class, "Unable to create address for sub-component of " + Msg.error(ReferenceUtils.class, "Unable to create address for sub-component of " +
data.getPathName() + " at " + dataAddress, e); pathName + " at " + dataAddress, e);
} }
return null; return null;
} }
@@ -1165,7 +1173,7 @@ public final class ReferenceUtils {
Consumer<LocationReference> consumer = Consumer<LocationReference> consumer =
locationReference -> accumulator.add(locationReference); locationReference -> accumulator.add(locationReference);
Program program = data.getProgram(); Program program = data.getProgram();
accumulateDirectReferences(consumer, program, dataAddress); accumulateDirectReferences(consumer, program, fieldMatcher, dataAddress);
Consumer<Reference> referenceConsumer = reference -> { Consumer<Reference> referenceConsumer = reference -> {
Address toAddress = reference.getToAddress(); Address toAddress = reference.getToAddress();
@@ -1177,7 +1185,8 @@ public final class ReferenceUtils {
// have a field match; only add the reference if it is directly to the field // have a field match; only add the reference if it is directly to the field
if (toAddress.equals(dataAddress)) { if (toAddress.equals(dataAddress)) {
accumulator.add(new LocationReference(reference, false)); String fieldName = fieldMatcher.getFieldName();
accumulator.add(new LocationReference(reference, false, fieldName));
} }
}; };
accumulateOffcutReferences(referenceConsumer, data, monitor); accumulateOffcutReferences(referenceConsumer, data, monitor);
@@ -1264,8 +1273,8 @@ public final class ReferenceUtils {
ReferenceManager referenceManager = program.getReferenceManager(); ReferenceManager referenceManager = program.getReferenceManager();
Reference[] variableRefsTo = referenceManager.getReferencesTo(variable); Reference[] variableRefsTo = referenceManager.getReferencesTo(variable);
for (Reference ref : variableRefsTo) { for (Reference ref : variableRefsTo) {
accumulator Address toAddress = ref.getToAddress();
.add(new LocationReference(ref, !ref.getToAddress().equals(variableAddress))); accumulator.add(new LocationReference(ref, !toAddress.equals(variableAddress)));
} }
} }
@@ -1348,12 +1357,18 @@ public final class ReferenceUtils {
private static void accumulateDirectReferences(Consumer<LocationReference> consumer, private static void accumulateDirectReferences(Consumer<LocationReference> consumer,
Program program, Address address) { Program program, Address address) {
accumulateDirectReferences(consumer, program, null, address);
}
private static void accumulateDirectReferences(Consumer<LocationReference> consumer,
Program program, FieldMatcher fieldMatcher, Address address) {
String fieldName = fieldMatcher != null ? fieldMatcher.getFieldName() : null;
boolean isOffcut = isOffcut(program, address); boolean isOffcut = isOffcut(program, address);
ReferenceIterator iter = program.getReferenceManager().getReferencesTo(address); ReferenceIterator iter = program.getReferenceManager().getReferencesTo(address);
while (iter.hasNext()) { while (iter.hasNext()) {
Reference ref = iter.next(); Reference ref = iter.next();
consumer.accept(new LocationReference(ref, isOffcut)); consumer.accept(new LocationReference(ref, isOffcut, fieldName));
} }
} }
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -72,7 +72,7 @@ class XRefLocationDescriptor extends LocationDescriptor {
} }
protected Address getXRefAddress(ProgramLocation location) { protected Address getXRefAddress(ProgramLocation location) {
return ((XRefFieldLocation) location).getRefAddress(); return location.getRefAddress();
} }
protected String getLabelForLocation(ProgramLocation location) { protected String getLabelForLocation(ProgramLocation location) {
@@ -58,6 +58,10 @@ public class DataTypeReference {
return context; return context;
} }
public String getFieldName() {
return fieldName;
}
@Override @Override
public String toString() { public String toString() {
String fieldNameText = fieldName == null ? "" : "\tfieldName: " + fieldName + "\n"; String fieldNameText = fieldName == null ? "" : "\tfieldName: " + fieldName + "\n";
@@ -1,48 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.decompile;
import java.util.function.Consumer;
import ghidra.app.services.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A simple spy to report when and how the finder service is called.
*/
public class StubDataTypeReferenceFinder implements DataTypeReferenceFinder {
@Override
public void findReferences(Program program, DataType dataType,
Consumer<DataTypeReference> callback, TaskMonitor monitor) throws CancelledException {
// stub
}
@Override
public void findReferences(Program program, DataType dataType, String fieldName,
Consumer<DataTypeReference> callback, TaskMonitor monitor) throws CancelledException {
// stub
}
@Override
public void findReferences(Program program, FieldMatcher fieldMatcher,
Consumer<DataTypeReference> callback, TaskMonitor monitor) throws CancelledException {
// stub
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -86,7 +86,8 @@ public class AnonymousVariableAccessDR extends VariableAccessDR {
if (fieldMatcher.isIgnored()) { if (fieldMatcher.isIgnored()) {
if (matchesFieldType) { if (matchesFieldType) {
// no field name and the search type matches this reference's field type // no field name and the search type matches this reference's field type
results.add(createReference(variable)); String fieldName = variable.getName();
results.add(createReference(variable, fieldName));
} }
// else there is no field and the search type does not match the reference's type // else there is no field and the search type does not match the reference's type
return; return;
@@ -64,7 +64,8 @@ public class VariableAccessDR extends DecompilerReference {
if (fields.isEmpty()) { if (fields.isEmpty()) {
DecompilerVariable var = getMatch(dt, fieldMatcher, variable, null); DecompilerVariable var = getMatch(dt, fieldMatcher, variable, null);
if (var != null) { if (var != null) {
DataTypeReference ref = createReference(var); String fieldName = null;
DataTypeReference ref = createReference(var, fieldName);
results.add(ref); results.add(ref);
} }
return; return;
@@ -87,17 +88,13 @@ public class VariableAccessDR extends DecompilerReference {
start = next; start = next;
} }
//
// Handle the last variable by itself (for the case where we are matching just on the type,
// with no field name)
//
if (fieldMatcher.isIgnored()) { if (fieldMatcher.isIgnored()) {
return; return;
} }
DecompilerVariable var = getMatch(dt, fieldMatcher, start, null); DecompilerVariable var = getMatch(dt, fieldMatcher, start, null);
if (var != null) { if (var != null) {
DataTypeReference ref = createReference(var); DataTypeReference ref = createReference(var, fieldMatcher.getFieldName());
results.add(ref); results.add(ref);
} }
} }
@@ -237,13 +234,13 @@ public class VariableAccessDR extends DecompilerReference {
return matches; return matches;
} }
protected DataTypeReference createReference(DecompilerVariable var) { protected DataTypeReference createReference(DecompilerVariable var, String fieldName) {
DataType dataType = var.getDataType(); DataType dataType = var.getDataType();
SearchLocationContext context = getContext(var); SearchLocationContext context = getContext(var);
Function function = var.getFunction(); Function function = var.getFunction();
Address address = getAddress(var); Address address = getAddress(var);
return new DataTypeReference(dataType, null, function, address, context); return new DataTypeReference(dataType, fieldName, function, address, context);
} }
private DataTypeReference createReference(DecompilerVariable var, DecompilerVariable field) { private DataTypeReference createReference(DecompilerVariable var, DecompilerVariable field) {
@@ -183,7 +183,8 @@ public class DecompilerTextFinderTableModel extends GhidraProgramTableModel<Text
return renderPlainContext(data, context); return renderPlainContext(data, context);
} }
return renderHtmlContext(data, context); // don't show line numbers since we have a separate column for that
return renderHtmlContext(data, context, false);
} }
} }
@@ -73,15 +73,6 @@ public class SearchLocationContext implements Comparable<SearchLocationContext>
this.parts = List.of(new BasicPart(context)); this.parts = List.of(new BasicPart(context));
} }
/**
* Constructor used to create this context by providing the given text parts
* @param parts the parts
* @see SearchLocationContextBuilder
*/
SearchLocationContext(List<Part> parts) {
this.parts = parts;
}
SearchLocationContext(List<Part> parts, int lineNumber) { SearchLocationContext(List<Part> parts, int lineNumber) {
this.parts = parts; this.parts = parts;
this.lineNumber = lineNumber; this.lineNumber = lineNumber;
@@ -164,7 +155,7 @@ public class SearchLocationContext implements Comparable<SearchLocationContext>
String lnText = ""; String lnText = "";
if (includeLineNumber) { if (includeLineNumber) {
lnText = getLineNumberText(false); lnText = getLineNumberText(true);
} }
StringBuilder buffy = new StringBuilder(lnText); StringBuilder buffy = new StringBuilder(lnText);
@@ -46,7 +46,10 @@ public abstract class SearchLocationContextRenderer
public Component getTableCellRendererComponent(GTableCellRenderingData data) { public Component getTableCellRendererComponent(GTableCellRenderingData data) {
SearchLocationContext context = getContext(data); SearchLocationContext context = getContext(data);
return renderHtmlContext(data, context);
// Note: we do not include the line number prefix on the text, based on the assumption that
// clients of this renderer will have a separate line number column.
return renderHtmlContext(data, context, false);
} }
public Component renderPlainContext(GTableCellRenderingData data, public Component renderPlainContext(GTableCellRenderingData data,
@@ -62,7 +65,7 @@ public abstract class SearchLocationContextRenderer
} }
public Component renderHtmlContext(GTableCellRenderingData data, public Component renderHtmlContext(GTableCellRenderingData data,
SearchLocationContext context) { SearchLocationContext context, boolean showLineNumbers) {
// initialize // initialize
super.getTableCellRendererComponent(data); super.getTableCellRendererComponent(data);
@@ -74,7 +77,7 @@ public abstract class SearchLocationContextRenderer
// Note: we do not include the line number prefix on the text, based on the assumption that // Note: we do not include the line number prefix on the text, based on the assumption that
// clients of this renderer will have a separate line number column. // clients of this renderer will have a separate line number column.
String html = context.getBoldMatchingText(false); String html = context.getBoldMatchingText(showLineNumbers);
setText(html); setText(html);
ellipsisLabel.setOpaque(true); ellipsisLabel.setOpaque(true);