GP-5475 changed composite field names so that any whitespace is converted to underscores

This commit is contained in:
ghidragon 2025-03-14 14:36:39 -04:00
parent 5a31ded2e0
commit 0ea4e754b9
10 changed files with 139 additions and 133 deletions

View file

@ -18,6 +18,7 @@ package ghidra.program.database.data;
import java.io.IOException;
import db.*;
import ghidra.util.StringUtilities;
import ghidra.util.exception.VersionException;
/**
@ -73,13 +74,16 @@ class ComponentDBAdapterV0 extends ComponentDBAdapter {
@Override
DBRecord createRecord(long dataTypeID, long parentID, int length, int ordinal, int offset,
String name, String comment) throws IOException {
// Don't allow whitespace in field names. Until we change the API to throw an exception
// when a field name has whitespace, just silently replace whitespace with underscores.
String fieldName = StringUtilities.whitespaceToUnderscores(name);
long key =
DataTypeManagerDB.createKey(DataTypeManagerDB.COMPONENT, componentTable.getKey());
DBRecord record = ComponentDBAdapter.COMPONENT_SCHEMA.createRecord(key);
record.setLongValue(ComponentDBAdapter.COMPONENT_PARENT_ID_COL, parentID);
record.setLongValue(ComponentDBAdapter.COMPONENT_OFFSET_COL, offset);
record.setLongValue(ComponentDBAdapter.COMPONENT_DT_ID_COL, dataTypeID);
record.setString(ComponentDBAdapter.COMPONENT_FIELD_NAME_COL, name);
record.setString(ComponentDBAdapter.COMPONENT_FIELD_NAME_COL, fieldName);
record.setString(ComponentDBAdapter.COMPONENT_COMMENT_COL, comment);
record.setIntValue(ComponentDBAdapter.COMPONENT_SIZE_COL, length);
record.setIntValue(ComponentDBAdapter.COMPONENT_ORDINAL_COL, ordinal);
@ -107,5 +111,4 @@ class ComponentDBAdapterV0 extends ComponentDBAdapter {
return componentTable.findRecords(new LongField(compositeID),
ComponentDBAdapter.COMPONENT_PARENT_ID_COL);
}
}

View file

@ -227,37 +227,12 @@ class DataTypeComponentDB implements InternalDataTypeComponent {
@Override
public void setFieldName(String name) throws DuplicateNameException {
if (record != null) {
name = checkFieldName(name);
record.setString(ComponentDBAdapter.COMPONENT_FIELD_NAME_COL, name);
String fieldName = cleanupFieldName(name);
record.setString(ComponentDBAdapter.COMPONENT_FIELD_NAME_COL, fieldName);
updateRecord(true);
}
}
private void checkDuplicateName(String name) throws DuplicateNameException {
DataTypeComponentImpl.checkDefaultFieldName(name);
for (DataTypeComponent comp : parent.getDefinedComponents()) {
if (comp == this) {
continue;
}
if (name.equals(comp.getFieldName())) {
throw new DuplicateNameException("Duplicate field name: " + name);
}
}
}
private String checkFieldName(String name) throws DuplicateNameException {
if (name != null) {
name = name.trim();
if (name.length() == 0 || name.equals(getDefaultFieldName())) {
name = null;
}
else {
checkDuplicateName(name);
}
}
return name;
}
@Override
public int hashCode() {
// It is not expected that these objects ever be put in a hash map
@ -431,9 +406,8 @@ class DataTypeComponentDB implements InternalDataTypeComponent {
if (StringUtils.isBlank(comment)) {
comment = null;
}
// TODO: Need to check field name and throw DuplicateNameException
// name = checkFieldName(name);
record.setString(ComponentDBAdapter.COMPONENT_FIELD_NAME_COL, name);
String fieldName = cleanupFieldName(name);
record.setString(ComponentDBAdapter.COMPONENT_FIELD_NAME_COL, fieldName);
record.setLongValue(ComponentDBAdapter.COMPONENT_DT_ID_COL, dataMgr.getResolvedID(dt));
record.setString(ComponentDBAdapter.COMPONENT_COMMENT_COL, comment);
updateRecord(false);

View file

@ -58,7 +58,7 @@ public class DataTypeComponentImpl implements InternalDataTypeComponent, Seriali
this.ordinal = ordinal;
this.offset = offset;
this.length = length;
this.fieldName = fieldName;
this.fieldName = cleanupFieldName(fieldName);
setDataType(dataType);
setComment(comment);
}
@ -130,32 +130,7 @@ public class DataTypeComponentImpl implements InternalDataTypeComponent, Seriali
@Override
public void setFieldName(String name) throws DuplicateNameException {
this.fieldName = checkFieldName(name);
}
private void checkDuplicateName(String name) throws DuplicateNameException {
checkDefaultFieldName(name);
if (parent == null) {
return; // Bad situation
}
for (DataTypeComponent comp : parent.getDefinedComponents()) {
if (comp != this && name.equals(comp.getFieldName())) {
throw new DuplicateNameException("Duplicate field name: " + name);
}
}
}
private String checkFieldName(String name) throws DuplicateNameException {
if (name != null) {
name = name.trim();
if (name.length() == 0 || name.equals(getDefaultFieldName())) {
name = null;
}
else {
checkDuplicateName(name);
}
}
return name;
this.fieldName = cleanupFieldName(name);
}
public static void checkDefaultFieldName(String fieldName) throws DuplicateNameException {
@ -192,22 +167,20 @@ public class DataTypeComponentImpl implements InternalDataTypeComponent, Seriali
/**
* Perform special-case component update that does not result in size or alignment changes.
* @param name new component name
* @param dt new resolved datatype
* @param cmt new comment
* @param newDataType new resolved datatype
* @param newComment new comment
*/
void update(String name, DataType dt, String cmt) {
// TODO: Need to check field name and throw DuplicateNameException
// this.fieldName = = checkFieldName(name);
this.fieldName = name;
this.dataType = dt;
this.comment = StringUtils.isBlank(cmt) ? null : cmt;
void update(String name, DataType newDataType, String newComment) {
this.fieldName = cleanupFieldName(name);
this.dataType = newDataType;
this.comment = StringUtils.isBlank(newComment) ? null : newComment;
}
@Override
public void update(int ordinal, int offset, int length) {
this.ordinal = ordinal;
this.offset = offset;
this.length = length;
public void update(int newOrdinal, int newOffset, int newLength) {
this.ordinal = newOrdinal;
this.offset = newOffset;
this.length = newLength;
}
/**

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.
@ -15,6 +15,10 @@
*/
package ghidra.program.model.data;
import org.apache.commons.lang3.StringUtils;
import ghidra.util.StringUtilities;
public interface InternalDataTypeComponent extends DataTypeComponent {
/**
@ -51,4 +55,19 @@ public interface InternalDataTypeComponent extends DataTypeComponent {
return buffer.toString();
}
/**
* Internal method for cleaning up field names.
* @param name the new field name
* @return the name with bad chars removed and also set back to null in the event
* the new name is the default name.
*/
public default String cleanupFieldName(String name) {
// For now, silently convert whitespace to underscores
String fieldName = StringUtilities.whitespaceToUnderscores(name);
if (StringUtils.isBlank(fieldName) || fieldName.equals(getDefaultFieldName())) {
fieldName = null;
}
return fieldName;
}
}

View file

@ -26,6 +26,7 @@ import com.google.common.collect.Sets;
import generic.test.AbstractGenericTest;
import ghidra.program.model.data.*;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
@ -1449,35 +1450,35 @@ public class StructureDBTest extends AbstractGenericTest {
assertEquals(dtc1, barStruct.getComponent(6));
}
@Test
public void testSetLength() {
assertEquals(8, struct.getLength());
assertEquals(4, struct.getNumComponents());
assertEquals(4, struct.getNumDefinedComponents());
struct.setLength(20);
assertEquals(20, struct.getLength());
assertEquals(16, struct.getNumComponents());
assertEquals(4, struct.getNumDefinedComponents());
// new length is offcut within 3rd component at offset 0x3 which should get cleared
struct.setLength(4);
assertEquals(4, struct.getLength());
assertEquals(3, struct.getNumComponents());
assertEquals(2, struct.getNumDefinedComponents());
// Maximum length supported by GUI editor is ~Integer.MAX_VALUE/10
int len = Integer.MAX_VALUE / 10;
struct.setLength(len);
assertEquals(len, struct.getLength());
assertEquals(len - 1, struct.getNumComponents());
assertEquals(2, struct.getNumDefinedComponents());
len /= 2;
struct.replaceAtOffset(len-2, WordDataType.dataType, -1, "x", null); // will be preserved below
struct.replaceAtOffset(len+2, WordDataType.dataType, -1, "y", null); // will be cleared below
struct.replaceAtOffset(len - 2, WordDataType.dataType, -1, "x", null); // will be preserved below
struct.replaceAtOffset(len + 2, WordDataType.dataType, -1, "y", null); // will be cleared below
struct.setLength(len);
assertEquals(len, struct.getLength());
assertEquals(len - 2, struct.getNumComponents());
@ -2616,4 +2617,23 @@ public class StructureDBTest extends AbstractGenericTest {
}
}
@Test
public void testFieldNameWhitespaceConvertedToUnderscores() throws DuplicateNameException {
StructureDataType newStruct = new StructureDataType("Test", 0);
DataTypeComponent component = newStruct.add(new ByteDataType(), " name with spaces", null);
assertEquals("name_with_spaces", component.getFieldName());
struct = (StructureDB) dataMgr.resolve(newStruct, null);
component = struct.getComponent(0);
component.setFieldName("name in db with spaces");
assertEquals("name_in_db_with_spaces", component.getFieldName());
component = struct.add(new ByteDataType(), "another test", null);
assertEquals("another_test", component.getFieldName());
struct.insert(0, new ByteDataType(), 1, "insert test", "");
component = struct.getComponent(0);
assertEquals("insert_test", component.getFieldName());
}
}