GP-6691 - Find Uses of - Fixed flaws in finding structure field uses in the DataTypeReference API

This commit is contained in:
dragonmacher
2026-06-11 12:20:17 -04:00
parent b66180204a
commit 773be4fc26
6 changed files with 394 additions and 117 deletions

View File

@@ -4,9 +4,9 @@
* 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.
@@ -94,7 +94,7 @@ public class FieldMatcher {
}
return dataType.getName() + " at " + fieldOffsets.toString();
}
return dataType.getName();
return dataType.getName() + " <matches all fields>";
}
private String generateCompositeFieldNameByOffset() {

View File

@@ -17,12 +17,15 @@ package ghidra.app.extension.datatype.finder;
import java.util.List;
import docking.widgets.search.SearchLocationContext;
import ghidra.app.decompiler.ClangFieldToken;
import ghidra.app.decompiler.ClangLine;
import ghidra.app.services.DataTypeReference;
import ghidra.app.services.FieldMatcher;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Function;
/**
* This class represents the use of a field of a {@link Composite} data type <b>where there is no
@@ -84,12 +87,18 @@ public class AnonymousVariableAccessDR extends VariableAccessDR {
// referring to the field and not the composite.
//
if (fieldMatcher.isIgnored()) {
if (matchesFieldType) {
// no field name and the search type matches this reference's field type
String fieldName = variable.getName();
results.add(createReference(variable, fieldName));
// no field to match and the search type matches this composite's field type
String fieldName = null;
DataTypeReference ref = createReferenceToVariable(variable, fieldName);
results.add(ref);
}
// else there is no field and the search type does not match the reference's type
// else there is no field to match and the search type matched the composite type; as
// mentioned in the comment above, we are ignoring the match on the composite, as
// that is handled by the VariableAccessDR, which gets created before this object as the
// tokens are being parsed.
return;
}
@@ -99,11 +108,20 @@ public class AnonymousVariableAccessDR extends VariableAccessDR {
// The client has requested a particular field of the parent composite. We only have a
// match if the parent type matches and the field name/offset matches.
//
String text = field.getText();
String fieldName = field.getText();
int offset = field.getOffset();
if (matchesCompositeType && fieldMatcher.matches(text, offset)) {
results.add(new DataTypeReference(compositeType, fieldMatcher.getFieldName(),
getFunction(), getAddress(), getContext()));
if (!fieldMatcher.matches(fieldName, offset)) {
return; // the field does not match
}
if (matchesCompositeType) {
Function function = getFunction();
Address address = getAddress();
SearchLocationContext context = getContext();
results.add(
new DataTypeReference(compositeType, fieldName, function, address, context));
}
// else, matches the composite's field type, but not the parent type; this fails the
// requirement that the composite type must match when searching for a field
}
}

View File

@@ -478,8 +478,12 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
DtrfDbg.println(function,
"\tcreating an anonymous variable access: " + line);
// this can happen when a field is used anonymously, such as directly
// after a nested array index operation
// This can happen when a field is used anonymously, such as directly
// after a nested array index operation. 'anonymous' here means that there
// is no named variable in the decompiler that is being dereferenced. In
// addition to seeing this for array accesses, we will also see this for
// nested composite access, like foo->bar->baz. 'baz' is being accessed
// anonymously.
results.add(new AnonymousVariableAccessDR(line, field));
continue;
}
@@ -497,15 +501,38 @@ public class DecompilerDataTypeReferenceFinder implements DataTypeReferenceFinde
return false; // should not happen
}
/*
Unusual Code: The data type of 'field' is usually the parent structure that contains
the field (not always though?). Also, apparently sometimes the data type of the
'variable' in 'access' is that of the field being accessed instead of the parent
structure. I'm guessing these conditions are bugs. Normally, the data type of
'variable' should be the parent containing type and the data type of 'field' should
be contained in the parent type.
This is used to determine when parent->child relationship is not correct so that we
can create an special access type to represent that.
*/
DataType parentType = field.getDataType();
DataType variableType = variable.getDataType();
if (DecompilerReference.isEqual(parentType, variableType)) {
return false; // the types match; this is the normal flow
}
// Note: the field's type is that of the parent structure, not the field. We want the
// field's type, so we must retrieve that.
DataType fieldDt = DecompilerReference.getFieldDataType(field);
DataType fieldType = DecompilerReference.getFieldDataType(field);
// check for the unusual case where the 'variable' type may be that of the field
if (DecompilerReference.isEqual(variableType, fieldType)) {
// Note: it would be nice to find a case(s) to verify this path
return false; // the types match; this is the normal flow
}
return true;
// unusual code: getDataType() on the variable may return the type of the field being
// accessed. Contrastingly, getDataType() on the field may return the
// type of the parent structure.
DataType variableDt = variable.getDataType();
return !DecompilerReference.isEqual(variableDt, fieldDt);
}
private VariableAccessDR getLastAccess(List<DecompilerReference> variables) {

View File

@@ -88,8 +88,7 @@ public abstract class DecompilerReference {
}
protected SearchLocationContext getContext() {
SearchLocationContext context = getContext(variable);
return context;
return getContext(variable);
}
protected SearchLocationContext getContext(DecompilerVariable var) {

View File

@@ -61,106 +61,310 @@ public class VariableAccessDR extends DecompilerReference {
public void accumulateMatches(DataType dt, FieldMatcher fieldMatcher,
List<DataTypeReference> results) {
if (fields.isEmpty()) {
DecompilerVariable var = getMatch(dt, fieldMatcher, variable, null);
if (var != null) {
String fieldName = null;
DataTypeReference ref = createReference(var, fieldName);
results.add(ref);
}
// simplify how we search depending on whether a field is required and if we have fields
if (fieldMatcher.isIgnored()) {
accumulateMatchesForTypeOnly(dt, results);
return;
}
//
// Walk each pair of type and variables in order to see if any of them match our
// criteria
//
DecompilerVariable start = variable;
if (fields.isEmpty()) {
// We have a valid field matcher, but no fields. Look for special case direct field
// usage, such as an enum name used inline, without its containing class.
accumulateMatchesDirectFields(dt, fieldMatcher, results);
return;
}
/*
This variable may have fields being accessed. We need to check for the data type usage
in this variable and each of the fields that we find. We need to make sure the fields
match as well. For the given text below we will ask the following questions:
Foo->bar->baz
- Does Foo match the type and 'bar' match the field matcher?
- Does bar match the type and 'baz' match the field matcher?
- Does baz match the type?
Known Special Cases:
A) Foo is reported as the same type as bar:
i. both are actually the desired type
ii. the field is not really the desired type
B) Foo's type is not a Foo, bar's type is Foo instead
*/
DecompilerVariable parent = variable;
for (DecompilerVariable field : fields) {
DecompilerVariable next = field;
DecompilerVariable var = getMatch(dt, fieldMatcher, start, next);
if (var != null) {
DataTypeReference ref = createReference(var, next);
// 1) See if the variable matches the type
DataTypeReference ref = createStandardReference(dt, parent, field, fieldMatcher);
if (ref != null) {
results.add(ref);
parent = field;
continue;
}
start = next;
// 2) See if both types match (case A from above)
// (At this point, either the parent type does not match, or the parent and the field
// share the same type.)
ref = createReferenceWhenVariableAndFieldMatch(dt, parent, field, fieldMatcher);
if (ref != null) {
results.add(ref);
parent = field;
continue;
}
// 3) See if the field matches the type (case B from above)
// (At this point the variable doesn't match, or the field doesn't match.)
ref = createFieldReference(dt, parent, field, fieldMatcher);
if (ref != null) {
results.add(ref);
parent = field;
continue;
}
}
if (fieldMatcher.isIgnored()) {
// Note: since the last field in the loop above does not itself have a field, then it
// cannot match the requirement of a parent type followed by a field match.
}
private void accumulateMatchesForTypeOnly(DataType dt, List<DataTypeReference> results) {
if (fields.isEmpty()) {
// No fields to check. See if this class's variable is a match.
DecompilerVariable var = getMatchingVariable(dt, variable);
if (var != null) {
String fieldName = null;
DataTypeReference ref = createReferenceToVariable(var, fieldName);
results.add(ref);
}
return;
}
DecompilerVariable var = getMatch(dt, fieldMatcher, start, null);
// This class's variable and each field may match the type. In this loop we will check each
// combination of a data type and the field, making a reference for all matches. Each pass
// through the loop makes the next field become the parent for the following field. The last
// field is handled below the loop.
DecompilerVariable parent = variable;
for (DecompilerVariable field : fields) {
DecompilerVariable var = getMatchingVariable(dt, parent);
parent = field;
if (var == null) {
continue; // the current 'parent' does not match
}
// Note: handle the bug condition where the variable and the field both have the type of
// interest.
DataTypeReference ref;
DecompilerVariable preferred = updateVarForBugCondition(var, field);
if (preferred == var) {
String fieldName = field.getName();
ref = createReferenceToVariable(var, fieldName);
}
else { // preferred == field
// if we encounter the bug condition where the field is the actual type we seek,
// then don't use the field name, as the field is the reference.
String fieldName = null;
ref = createReferenceToField(preferred, fieldName);
}
results.add(ref);
}
// Handle the last field. In this case, we cannot have a parent and field match, only a
// match on the data type with no field.
DecompilerVariable var = getMatchingVariable(dt, parent);
if (var != null) {
DataTypeReference ref = createReference(var, fieldMatcher.getFieldName());
String fieldName = null;
DataTypeReference ref = createReferenceToVariable(var, fieldName);
results.add(ref);
}
}
private DecompilerVariable updateVarForBugCondition(DecompilerVariable var,
DecompilerVariable field) {
if (isFieldTheBetterMatch(var, field)) {
// assume a real match for a self-referencing structure
return field;
}
return var;
}
private boolean isFieldTheBetterMatch(DecompilerVariable var, DecompilerVariable field) {
// This is an odd case where both the parent and the field have the same type. This may be
// valid, such as when a structure contains a reference to itself. But, this also can
// happen when the decompiler makes mistakes when setting the data type.
// This type may not be the actual field type. We check on that below when these types are
// the same.
DataType fdt = field.getDataType();
DataType vdt = var.getDataType();
if (!isEqual(vdt, fdt)) {
return false; // the original var is the correct variable
}
// We have the same type. If the 2 types are really the same (self-refernecing structure),
// then we want a match for the parent to the field. If the types are not really the same,
// then for now assume the field itself is the correct match.
// Note: this had been using 'sourceToken', which doesn't seem right. If we find missed
// cases where 'field.variable' is not working, then revisit using 'sourceToken' as a
// fallback check.
//ClangFieldToken fieldToken = (ClangFieldToken) sourceToken;
if (!(field.variable instanceof ClangFieldToken fieldToken)) {
return false; // cannot check the field's type
}
DataType actualFieldDt = DecompilerReference.getFieldDataType(fieldToken);
boolean matchesFieldType = isEqual(vdt, actualFieldDt);
if (matchesFieldType) {
// assume a real match for a self-referencing structure
return true;
}
return false;
}
private void accumulateMatchesDirectFields(DataType dt, FieldMatcher fieldMatcher,
List<DataTypeReference> results) {
DecompilerVariable var = getMatch(dt, fieldMatcher, variable);
if (var != null) {
String fieldName = fieldMatcher.getFieldName();
DataTypeReference ref = createReferenceToField(var, fieldName);
results.add(ref);
}
}
private DataTypeReference createStandardReference(DataType dt, DecompilerVariable parent,
DecompilerVariable field, FieldMatcher fieldMatcher) {
// a standard reference is the case where the parent is the type we seek and the field
// matches the matcher
DecompilerVariable var = getMatchingVariable(dt, parent);
if (var == null) {
return null;
}
DataType vdt = var.getDataType();
DataType fdt = field.getDataType();
if (isEqual(vdt, fdt)) {
return null; // this may be an error case; handled by a different method later
}
String fieldName = field.getName();
int offset = field.getOffset();
if (fieldMatcher.matches(fieldName, offset)) {
return createReference(var, field, fieldMatcher);
}
return null;
}
private DataTypeReference createFieldReference(DataType dt, DecompilerVariable parent,
DecompilerVariable field, FieldMatcher fieldMatcher) {
// Note: this method should only be called if we did not create a 'standard reference',
// which is when the parent type matches 'dt' and the field type does not match. Getting
// into this method means either the parent type did not match, or the parent and field both
// match the type.
/*
This is the case where the parent variable was not the correct type, but we need to see
if the field itself is the type we seek. I believe this comment describes this case:
// check for the case where the given 'field' may be the type we seek, such as in an if
// statement like this:
// if (color == RED)
// where 'RED' is the variable we are checking
*/
HighVariable highVariable = parent.variable.getHighVariable();
if (highVariable == null) {
return null; // not sure of the significance of this check
}
if (!matchesParentType(field, dt)) {
return null;
}
String fieldName = field.getName();
int offset = field.getOffset();
if (fieldMatcher.matches(fieldName, offset)) {
// the field matches the type and the name
return createReference(field, field, fieldMatcher);
}
return null;
}
private DataTypeReference createReferenceWhenVariableAndFieldMatch(DataType dt,
DecompilerVariable parent, DecompilerVariable field, FieldMatcher fieldMatcher) {
// This is an odd case where both the parent and the field have the same type. This may be
// valid, such as when a structure contains a reference to itself. But, this also can
// happen when the decompiler makes mistakes when setting the data type.
DecompilerVariable var = getMatchingVariable(dt, parent);
if (var == null) {
return null;
}
String name = field.getName();
int offset = field.getOffset();
if (!fieldMatcher.matches(name, offset)) {
return null;
}
if (isFieldTheBetterMatch(var, field)) {
// this implies a self-referential structure; create a reference to the field
return createReference(field, field, fieldMatcher);
}
// At this point, the parent matches the data type, but the field itself is not actually
// a data type match. Also, since we checked above, we know the field name matches. Create
// a standard reference.
return createReference(var, field, fieldMatcher);
}
private DecompilerVariable getMatch(DataType dt, FieldMatcher fieldMatcher,
DecompilerVariable var, DecompilerVariable potentialField) {
DecompilerVariable var) {
String indent = "\t\t\t";
// Note: for now, I ignore the precedence of casting; if any cast type is a match, then
// signal hooray
boolean searchForField = !fieldMatcher.isIgnored();
DecompilerVariable fieldVar = searchForField ? potentialField : null;
DecompilerVariable match = getMatchingVarialbe(dt, var, fieldVar);
DecompilerVariable match = getMatchingVariable(dt, var);
if (match == null) {
DtrfDbg.println(getFunction(), this, indent + "NO MATCHING VARIABLE");
return null; // wrong type, nothing to do
}
// Matches on the type, does the field match?
if (fieldMatcher.isIgnored()) {
DtrfDbg.println(getFunction(), this,
indent + "field macher is ignored; returning match");
return match; // no field to match
}
if (potentialField == null) {
DtrfDbg.println(getFunction(), this,
indent + "No potential field to match; name / offset match?");
// check for the case where we have not been passed a 'potential field', but the given
// 'var' is itself may be the field we seek, such as in an if statement like this:
// if (color == RED)
// where 'RED' is the variable we are checking
String name = var.getName();
int offset = var.getOffset();
if (fieldMatcher.matches(name, offset)) {
StringUtilities.indentLines(var.toString(), indent + '\t');
DtrfDbg.println(getFunction(), this,
indent + "\tfield matcher matched on variable: " + var);
return var;
}
DtrfDbg.println(getFunction(), this, indent + "\tNO FIELD MATCHER MATCH");
return null; // we seek a field, but there is none
}
DtrfDbg.println(getFunction(), this, indent + "Checking 'potential field' match...");
String name = potentialField.getName();
int offset = potentialField.getOffset();
// check for the case where we have not been passed a 'potential field', but the given
// 'var' is itself may be the field we seek, such as in an if statement like this:
// if (color == RED)
// where 'RED' is the variable we are checking
String name = var.getName();
int offset = var.getOffset();
if (fieldMatcher.matches(name, offset)) {
DtrfDbg.println(getFunction(), this, indent + "\tMATCHED");
return match;
StringUtilities.indentLines(var.toString(), indent + '\t');
DtrfDbg.println(getFunction(), this,
indent + "\tfield matcher matched on variable: " + var);
return var;
}
DtrfDbg.println(getFunction(), this, indent + "\tNO MATCH");
return null;
DtrfDbg.println(getFunction(), this, indent + "\tNO FIELD MATCHER MATCH");
return null; // we seek a field, but there is none
}
private DecompilerVariable getMatchingVarialbe(DataType dt, DecompilerVariable var,
DecompilerVariable potentialField) {
// return the first matching cast for the given variable, if any
private DecompilerVariable getFirstMatchingCast(DataType dt, DecompilerVariable var) {
String indent = "\t\t\t";
DtrfDbg.println(getFunction(), this, indent + "Checking for matching variable; any casts?");
DtrfDbg.println(getFunction(), this, indent +
"Checking for matching variable (no fields); any casts?");
List<DecompilerVariable> castVariables = var.getCasts();
for (DecompilerVariable cast : castVariables) {
if (matchesType(cast, dt)) {
@@ -168,6 +372,20 @@ public class VariableAccessDR extends DecompilerReference {
return cast;
}
}
return null;
}
private DecompilerVariable getMatchingVariable(DataType dt, DecompilerVariable var) {
String indent = "\t\t\t";
// Note: for now, we ignore the precedence of casting; if any cast type is a match, then
// signal hooray
DecompilerVariable cast = getFirstMatchingCast(dt, var);
if (cast != null) {
// assume that any match in the cast implies we will not match a variable access
return cast;
}
String dtString = dt == null ? "null" : dt.toString();
DtrfDbg.println(getFunction(), this,
@@ -178,29 +396,6 @@ public class VariableAccessDR extends DecompilerReference {
DtrfDbg.println(getFunction(), this, indent + "MATCHED type: ");
return var;
}
DtrfDbg.println(getFunction(), this,
indent + "Type did not match; checking High Variable: ");
//
// Unusual Code Alert!
// It is a bit odd to check the field when you are looking for the type that contains the
// field. BUT, in the Decompiler, SOMETIMES the 'field' happens to have the data type of
// the thing that contains it. So, if you have:
// foo.bar
// then the 'bar' field will have a data type of Foo. Unfortunately, this is not always
// the case. For now, if there is a high variable, we need to check the field. Sad face
// emoji.
//
HighVariable highVariable = var.variable.getHighVariable();
if (highVariable != null) {
if (matchesParentType(potentialField, dt)) {
DtrfDbg.println(getFunction(), this, indent + "MATCHED on parent type: " + dt);
return potentialField;
}
}
DtrfDbg.println(getFunction(), this, indent + "NOT MATCHED");
return null;
}
@@ -227,37 +422,70 @@ public class VariableAccessDR extends DecompilerReference {
if (varType == null) {
// it seems odd to me that there is no type, but I have seen this in the case
// statement of a switch
DtrfDbg.println(getFunction(), this, indent + "ypes Match? no variable TYPE to check");
DtrfDbg.println(getFunction(), this, indent + "Types Match? no variable TYPE to check");
return false;
}
boolean matches = isEqual(varType, dt);
return matches;
}
protected DataTypeReference createReference(DecompilerVariable var, String fieldName) {
protected DataTypeReference createReferenceToVariable(DecompilerVariable var,
String fieldName) {
DataType dataType = var.getDataType();
SearchLocationContext context = getContext(var);
SearchLocationContext context = getContextForVariable(var);
Function function = var.getFunction();
Address address = getAddress(var);
return new DataTypeReference(dataType, fieldName, function, address, context);
}
private DataTypeReference createReference(DecompilerVariable var, DecompilerVariable field) {
protected DataTypeReference createReferenceToField(DecompilerVariable field,
String fieldName) {
DataType dataType = field.getDataType();
SearchLocationContext context = getContextForField(field);
Function function = field.getFunction();
Address address = getAddress(field);
return new DataTypeReference(dataType, fieldName, function, address, context);
}
private DataTypeReference createReference(DecompilerVariable var, DecompilerVariable field,
FieldMatcher fieldMatcher) {
DataType dataType = var.getDataType();
SearchLocationContext context = getContext(var);
SearchLocationContext context = getContext(var, fieldMatcher);
Function function = var.getFunction();
Address address = getAddress(var);
return new DataTypeReference(dataType, field.getName(), function, address, context);
String fieldName = field != null ? field.getName() : null;
return new DataTypeReference(dataType, fieldName, function, address, context);
}
@Override
protected SearchLocationContext getContext(DecompilerVariable var) {
// Default to highlighting the field that is being called on the given variable. Clients of
// this method may choose to highlight the variable itself, in which case they should call
// getContextForVariable(var).
return getContextForField(var);
}
private SearchLocationContext getContextForVariable(DecompilerVariable var) {
return super.getContext(var);
}
protected SearchLocationContext getContextForField(DecompilerVariable var) {
DecompilerVariable field = findFieldFor(var);
SearchLocationContext context = super.getContext(field);
return context;
}
protected SearchLocationContext getContext(DecompilerVariable var, FieldMatcher fieldMatcher) {
if (fieldMatcher.isIgnored()) {
// when there is not field to match, we only want to highlight the variable itself
return super.getContext(var);
}
// assume that a valid field matcher means that we have already matched before this call,
// so use the matching field for the context highlight
return getContextForField(var);
}
private DecompilerVariable findFieldFor(DecompilerVariable var) {
//
@@ -308,6 +536,10 @@ public class VariableAccessDR extends DecompilerReference {
@Override
public String toString() {
if (variable == null) {
return "<uninialized>";
}
String subFieldsString = fields.isEmpty() ? ""
: "\tsub fields: " + StringUtilities.toStringWithIndent(fields) + ",\n";

View File

@@ -78,4 +78,5 @@ public abstract class VariableDR extends DecompilerReference {
results.add(new DataTypeReference(dataType, fieldName, function, address, context));
}
}
}