diff --git a/Ghidra/Features/VersionTracking/src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Apply_Options.html b/Ghidra/Features/VersionTracking/src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Apply_Options.html index fa3160d0bd..dd7625325f 100644 --- a/Ghidra/Features/VersionTracking/src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Apply_Options.html +++ b/Ghidra/Features/VersionTracking/src/main/help/help/topics/VersionTrackingPlugin/providers/VT_Apply_Options.html @@ -458,7 +458,7 @@

- False + True diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/markuptype/FunctionNameMarkupType.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/markuptype/FunctionNameMarkupType.java index e3a5d7a2a2..392b099bfc 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/markuptype/FunctionNameMarkupType.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/markuptype/FunctionNameMarkupType.java @@ -279,7 +279,8 @@ public class FunctionNameMarkupType extends FunctionEntryPointBasedAbstractMarku } Function srcFunction = getSourceFunction(markupItem.getAssociation()); - boolean replaceNamespace = markupOptions.getBoolean(USE_NAMESPACE_FUNCTIONS, false); + boolean replaceNamespace = markupOptions.getBoolean(USE_NAMESPACE_FUNCTIONS, + DEFAULT_OPTION_FOR_NAMESPACE_FUNCTIONS); if (!hasAnythingToApply(srcStringable, srcFunction, replaceNamespace)) { return false; } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/markuptype/FunctionSignatureMarkupType.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/markuptype/FunctionSignatureMarkupType.java index bb234def63..a691c70a45 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/markuptype/FunctionSignatureMarkupType.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/markuptype/FunctionSignatureMarkupType.java @@ -188,6 +188,7 @@ public class FunctionSignatureMarkupType extends FunctionEntryPointBasedAbstract throw new VersionTrackingApplyException( "Couldn't find destination function to apply a name."); } + if (sourceStringable.applyFunctionSignature(destinationFunction, markupOptions, false)) { return true; } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/stringable/FunctionSignatureStringable.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/stringable/FunctionSignatureStringable.java index 4453fe85a3..fb3d34edec 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/stringable/FunctionSignatureStringable.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/stringable/FunctionSignatureStringable.java @@ -546,6 +546,11 @@ public class FunctionSignatureStringable extends Stringable { useCustomStorage ? FunctionUpdateType.CUSTOM_STORAGE : FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, signatureSource); + + // Test to see if destination function is now in the same namespace as the source + // if so copy the class structure from source to destination if the update didn't + copyClassStructure(newParams, toFunction, markupOptions); + if (forceApply) { // must force signatureSource if precedence has been lowered // TODO: Should any manual change in function signature force source to be USER_DEFINED instead ?? @@ -578,6 +583,195 @@ public class FunctionSignatureStringable extends Stringable { return true; } + /** + * Method to determine if a copy is needed of the source class struct to the destination program + * and if so, does the copy + * @param newParams the params to apply to the destination program as determined by the apply + * options + * @param toFunction the destination function + */ + private void copyClassStructure(List newParams, Function toFunction, + ToolOptions markupOptions) { + + // this is only meant to handle the case where there are auto this params + // existing other mechanisms handle custom storage case already + if (toFunction.hasCustomVariableStorage()) { + return; + } + + VTMatchApplyChoices.ParameterDataTypeChoices parameterDataTypesChoice = + markupOptions.getEnum(VTOptionDefines.PARAMETER_DATA_TYPES, + DEFAULT_OPTION_FOR_PARAMETER_DATA_TYPES); + + if (parameterDataTypesChoice == ParameterDataTypeChoices.EXCLUDE) { + return; + } + + boolean onlyReplaceUndefineds = + (parameterDataTypesChoice == ParameterDataTypeChoices.REPLACE_UNDEFINED_DATA_TYPES_ONLY); + + boolean replaceAlways = (parameterDataTypesChoice == ParameterDataTypeChoices.REPLACE); + + // check to see if the resulting newParams after checking the markupOptions includes + // a this param and if so resolve the class data type in the destination program + // to make sure the class structure gets copied to the destination program + + // if source function is not a thiscall then no class structure to copy + if (!callingConventionName.equals(CompilerSpec.CALLING_CONVENTION_thiscall)) { + return; + } + // if there are no new params to apply as determined by the options then nothing to copy + if (newParams.isEmpty()) { + return; + } + + // if newParams does not have a this param to copy as determined by the apply options then + // no copy needed + if (!newParams.get(0).getName().equals("this")) { + return; + } + + // get the this param for source and verify it is a pointer to a structure + ParameterInfo sourceParam1 = parameterInfos.get(0); + DataType sourceClassDataType = getPointedToDataType(sourceParam1.dataType); + if (sourceClassDataType == null) { + return; + } + + if (!isStructure(sourceClassDataType)) { + return; + } + + // get the pointed to data type for the new destination this param + // if it isn't a pointer to a data type then return + Parameter newDestinationParam1 = newParams.get(0); + DataType newDestinationClassDataType = + getPointedToDataType(newDestinationParam1.getDataType()); + if (newDestinationClassDataType == null) { + return; + } + + // if the new this param (ie what the calling method has determined what the new this + // param is supposed to be) is not a pointer to a structure then return because it + // doesn't think it should also be a class structure + if (!isStructure(newDestinationClassDataType)) { + return; + } + + // if it gets this far then can probably assume both the new destination and source this + // data types are pointers to class structures + // NOTE: at this time there is a use case where the calling method incorrectly assumes the + // newParam is the same class structure as the source one. However, that method did not + // check the FunctionName option to see if that option was to not replace the function + // name. So we need to have a check to see if the current function namespace is already + // the same as the assumed new destination this structure name which would indicate that + // the function already was in the same class and so we still need to do the resolve to make + // sure that the structure contents get resolved according to the parameter replace options + VTMatchApplyChoices.FunctionNameChoices functionNameChoice = + markupOptions.getEnum(VTOptionDefines.FUNCTION_NAME, DEFAULT_OPTION_FOR_FUNCTION_NAME); + String currentDestinationFunctionNamespaceName = + toFunction.getSymbol().getParentNamespace().getName(); + String newDestinationClassName = newDestinationClassDataType.getName(); + if (functionNameChoice == FunctionNameChoices.EXCLUDE && + !currentDestinationFunctionNamespaceName.equals(newDestinationClassName)) { + return; + } + + // now use the path to the source data type to try and get the same named structure + // in the destination data type manager + ProgramBasedDataTypeManager destinationDataTypeManager = + toFunction.getProgram().getDataTypeManager(); + + String pathName = sourceClassDataType.getPathName(); + DataType destinationDataTypeInDTM = destinationDataTypeManager.getDataType(pathName); + + // only do the copy if the option is to replace always or if it is replace undefines and the + // destination structure is empty (ie undefined) + if (replaceAlways || + destinationDataTypeInDTM == null || + (destinationDataTypeInDTM.isNotYetDefined() && onlyReplaceUndefineds)) { + + // since nothing above prevents the copy go ahead and copy the source class data type + + //needs to be the original dt to get the struct * not just the struct + destinationDataTypeManager.resolve(sourceParam1.dataType, + DataTypeConflictHandler.REPLACE_EMPTY_STRUCTS_OR_RENAME_AND_ADD_HANDLER); + + // check to see if the resolve probably updated the function's auto this param datatype + // it might not have if there was a .conflict created during the resolve or if the + // source and destination data types were in different folders or if one program + // has the preferred root data type manager folder set and the other doesn't + // not sure there is a way to tell this info since I don't know which the decompiler will + // pick at this point because nothing has been applied yet + DataType destinationDataTypeConflict = + destinationDataTypeManager.getDataType(pathName + ".conflict"); + + if (destinationDataTypeConflict != null) { + Msg.debug(this, "The applied class structure " + + newDestinationClassDataType.getPathName() + + " was copied to the destination program but a .conflict was created due to there " + + "already being a non-empty structure with that same path and name."); + } + + // get the original toFunction this param if there was one and get it's path + // if the new path is different then spit out warming too + if (toFunction.getParameterCount() == 0) { + return; + } + + if (!toFunction.getParameter(0).getName().equals("this")) { + return; + } + + DataType pointedToDataType = + getPointedToDataType(toFunction.getParameter(0).getDataType()); + if (pointedToDataType == null) { + return; + } + + if (!pointedToDataType.getName().equals(newDestinationClassName)) { + return; + } + + if (pointedToDataType.getPathName().equals(pathName)) { + return; // already handled with conflict check above + } + + Msg.debug(this, + "The applied class structure was copied to the same data type manager " + + "path as in the source program whichi is different than the path to the existing " + + "class structure. The decompiler will first check for one in the Preferred Class" + + "Root Folder (if one has been set) otherwise it will use the first one it finds."); + } + + } + + private boolean isStructure(DataType dataType) { + + if (dataType instanceof Structure) { + return true; + } + + if (dataType instanceof StructureDataType) { + return true; + } + + return false; + + } + + private DataType getPointedToDataType(DataType dataType) { + + if (!(dataType instanceof PointerDataType)) { + return null; + } + + PointerDataType pointerDataType = (PointerDataType) dataType; + + return pointerDataType.getDataType(); + + } + private void applyInline(Function toFunction, boolean fromFunctionIsInline, ToolOptions markupOptions) { ReplaceChoices inlineChoice = markupOptions.getEnum(INLINE, DEFAULT_OPTION_FOR_INLINE); @@ -604,10 +798,12 @@ public class FunctionSignatureStringable extends Stringable { return returnParam; // Not replacing return type. } DataType toReturnType = toFunction.getReturnType(); + DataType fromReturnType = returnInfo.dataType; boolean isFromDefault = fromReturnType == DataType.DEFAULT; boolean isToDefault = toReturnType == DataType.DEFAULT; - boolean isToUndefined = Undefined.isUndefined(toReturnType); + boolean isToUndefined = Undefined.isUndefined(getBaseDataType(toReturnType)); + if (!forceApply && onlyReplaceUndefineds) { if (!isToDefault && !isToUndefined) { return returnParam; // can't do it because we should only replace undefined data types. @@ -773,13 +969,28 @@ public class FunctionSignatureStringable extends Stringable { return parameters; } + /** + * If the given data type is a pointer, get the "pointed to" data type + * @param dataType the given data type + * @return if not a pointer, just return the same dataType, if a pointer, return the + * "pointed to" data type + */ + private DataType getBaseDataType(DataType dataType) { + + if (dataType instanceof Pointer) { + Pointer pointer = (Pointer) dataType; + dataType = pointer.getDataType(); + } + return dataType; + } + private DataType getHighestPriorityDataType(DataType fromDataType, DataType toDataType, boolean onlyReplaceUndefineds) { // Priority from highest to lowest is Defined, Undefined with size, Default. boolean fromIsDefault = fromDataType == DataType.DEFAULT; boolean toIsDefault = toDataType == DataType.DEFAULT; - boolean fromIsUndefined = Undefined.isUndefined(fromDataType); - boolean toIsUndefined = Undefined.isUndefined(toDataType); + boolean fromIsUndefined = Undefined.isUndefined(getBaseDataType(fromDataType)); + boolean toIsUndefined = Undefined.isUndefined(getBaseDataType(toDataType)); if (fromIsDefault) { return toDataType; } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/ApplyMarkupPropertyEditor.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/ApplyMarkupPropertyEditor.java index d336e34f63..688c95243a 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/ApplyMarkupPropertyEditor.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/ApplyMarkupPropertyEditor.java @@ -58,8 +58,6 @@ public class ApplyMarkupPropertyEditor implements OptionsEditor { private static final String FUNCTION_SIGNATURE_TOOLTIP = "The apply action for the function signature " + "when performing bulk apply operations"; - private static final String USE_NAMESPACE_TOOLTIP = - "When true function namespaces will be applied when names are applied."; private static final String PLATE_COMMENT_TOOLTIP = "The apply action for plate comments when performing bulk apply operations"; private static final String PRE_COMMENT_TOOLTIP = diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java index f03903c6ad..abba8cf0bc 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java @@ -767,7 +767,7 @@ public class VTMatchTableProvider extends ComponentProviderAdapter "should become ignored by applying a match."); vtOptions.registerOption(USE_NAMESPACE_FUNCTIONS, DEFAULT_OPTION_FOR_NAMESPACE_FUNCTIONS, - null, "Apply the non-Global source namespace to the destination function."); + null, USE_NAMESPACE_TOOLTIP); vtOptions.getOptions(APPLY_MARKUP_OPTIONS_NAME) .registerOptionsEditor(() -> new ApplyMarkupPropertyEditor(controller)); diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/util/VTOptionDefines.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/util/VTOptionDefines.java index 999ce5d331..2ad0385fa9 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/util/VTOptionDefines.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/util/VTOptionDefines.java @@ -113,6 +113,9 @@ public class VTOptionDefines { public static final String USE_NAMESPACE_FUNCTIONS = APPLY_MARKUP_OPTIONS_NAME + ".Replace Namespace"; + public static final String USE_NAMESPACE_TOOLTIP = + "Apply the non-Global source namespace to the destination function."; + // Auto VT Options public static final String AUTO_VT_OPTIONS_NAME = "Auto Version Tracking Options"; diff --git a/Ghidra/Features/VersionTracking/src/test.slow/java/ghidra/feature/vt/api/VTMatchApplyFunctionSignatureTest.java b/Ghidra/Features/VersionTracking/src/test.slow/java/ghidra/feature/vt/api/VTMatchApplyFunctionSignatureTest.java index 96d193dddc..3cd2e27172 100644 --- a/Ghidra/Features/VersionTracking/src/test.slow/java/ghidra/feature/vt/api/VTMatchApplyFunctionSignatureTest.java +++ b/Ghidra/Features/VersionTracking/src/test.slow/java/ghidra/feature/vt/api/VTMatchApplyFunctionSignatureTest.java @@ -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. @@ -39,6 +39,7 @@ import ghidra.program.model.address.Address; import ghidra.program.model.data.*; import ghidra.program.model.lang.*; import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function.FunctionUpdateType; import ghidra.program.model.symbol.SourceType; import ghidra.program.util.DefaultLanguageService; import ghidra.test.AbstractGhidraHeadedIntegrationTest; @@ -124,6 +125,38 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg return struct; } + //Create Gadget structure - slightly different than usual in last field to simplify test + private Structure createGadgetStruct() { + + Structure gadgetStruct = new StructureDataType("Gadget", 0); + PointerDataType charPtr = new PointerDataType(new CharDataType()); + gadgetStruct.add(charPtr, "name", ""); + gadgetStruct.add(new IntegerDataType(), "type", ""); + gadgetStruct.add(new BooleanDataType(), "deployed", ""); + gadgetStruct.add(new DWordDataType(), "workingOn", ""); + + return gadgetStruct; + + } + + private Structure createDifferentGadgetStruct() { + + Structure gadgetStruct = new StructureDataType("Gadget", 0); + PointerDataType charPtr = new PointerDataType(new CharDataType()); + gadgetStruct.add(charPtr, "name", ""); + gadgetStruct.add(new IntegerDataType(), "type", ""); + gadgetStruct.add(new BooleanDataType(), "deployed", ""); + gadgetStruct.add(new PointerDataType(), "workingOn", ""); + + return gadgetStruct; + + } + + private Structure createEmptyGadgetStruct() { + Structure gadgetStruct = new StructureDataType("Gadget", 0); + return gadgetStruct; + } + private Program createSourceProgram() throws Exception { ProgramBuilder builder = new ProgramBuilder("Wallace", ProgramBuilder._X86, this); @@ -133,6 +166,7 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg builder.createClassNamespace("Gadget", null, SourceType.IMPORTED); StructureDataType struct = getPersonStruct(p); + builder.addDataType(struct); Pointer ptr1 = PointerDataType.getPointer(struct, p.getDataTypeManager()); Pointer ptr2 = PointerDataType.getPointer(ptr1, p.getDataTypeManager()); @@ -291,6 +325,7 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg @Test public void testApplyMatch_ReplaceSignature_SameNumParams_ThisToThis() throws Exception { + useMatch("0x00401040", "0x00401040"); // Check initial values @@ -314,6 +349,8 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg List matches = new ArrayList<>(); matches.add(testMatch); + checkClassDataType(false, false); + // Test Apply ApplyMatchTask task = new ApplyMatchTask(controller, matches); runTask(session, task); @@ -324,6 +361,10 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg assertEquals(VTAssociationStatus.ACCEPTED, testMatch.getAssociation().getStatus()); checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.REPLACED); + // since no option to apply function name there will be no class namespace applied to + // the destination so no Gadget class data type should exist + checkClassDataType(false, false); + // Test unapply ClearMatchTask unapplyTask = new ClearMatchTask(controller, matches); runTask(session, unapplyTask); @@ -333,6 +374,464 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg "undefined FUN_00401040(void * this, undefined4 param_1)"); assertEquals(VTAssociationStatus.AVAILABLE, testMatch.getAssociation().getStatus()); checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.UNAPPLIED); + + } + + // use case: test replace undefined for return and params when they are undefined ptrs + @Test + public void testApplyMatch_ReplaceSignature_SameNumParams_ThisToThis_ReplaceUndefinedPointers() + throws Exception { + + useMatch("0x00401040", "0x00401040"); + + // update the source function to have a Gadget * type + + Function srcFunction = sourceProgram.getFunctionManager() + .getFunctionAt(addr("0x00401040", sourceProgram)); + + DataType existingEmptyGadgetStruct = + sourceProgram.getDataTypeManager().getDataType(CategoryPath.ROOT, "Gadget"); + assertNotNull(existingEmptyGadgetStruct); + + Pointer ptr1 = + PointerDataType.getPointer(existingEmptyGadgetStruct, + sourceProgram.getDataTypeManager()); + + assertNotNull(ptr1); + + int id = sourceProgram.startTransaction("Test"); + + srcFunction.setReturnType(ptr1, SourceType.USER_DEFINED); + + sourceProgram.endTransaction(id, true); + + // update the destination function to have undefined4 * return type and to have param_1 + // also be a undefined4 * + + id = destinationProgram.startTransaction("Test"); + + Function destFunction = destinationProgram.getFunctionManager() + .getFunctionAt(addr("0x00401040", destinationProgram)); + ptr1 = + PointerDataType.getPointer(Undefined4DataType.dataType, + destinationProgram.getDataTypeManager()); + destFunction.setReturnType(ptr1, SourceType.DEFAULT); + + Parameter[] parameters = destFunction.getParameters(); + parameters[1].setDataType(ptr1, parameters[1].getSource()); + + destFunction.replaceParameters(FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, + SourceType.USER_DEFINED, parameters); + + destinationProgram.endTransaction(id, true); + + // Check initial values + checkSignatures("Gadget * use(Gadget * this, Person * person)", + "undefined4 * FUN_00401040(void * this, undefined4 * param_1)"); + + // Set the function signature options for this test + ToolOptions applyOptions = controller.getOptions(); + applyOptions.setEnum(VTOptionDefines.FUNCTION_NAME, + FunctionNameChoices.REPLACE_ALWAYS); + applyOptions.setEnum(FUNCTION_SIGNATURE, + FunctionSignatureChoices.WHEN_SAME_PARAMETER_COUNT); + applyOptions.setEnum(CALLING_CONVENTION, CallingConventionChoices.SAME_LANGUAGE); + applyOptions.setEnum(PARAMETER_DATA_TYPES, + ParameterDataTypeChoices.REPLACE_UNDEFINED_DATA_TYPES_ONLY); + applyOptions.setEnum(PARAMETER_NAMES, SourcePriorityChoices.REPLACE); + applyOptions.setEnum(PARAMETER_COMMENTS, CommentChoices.APPEND_TO_EXISTING); + applyOptions.setEnum(NO_RETURN, ReplaceChoices.EXCLUDE); + applyOptions.setEnum(FUNCTION_RETURN_TYPE, + ParameterDataTypeChoices.REPLACE_UNDEFINED_DATA_TYPES_ONLY); + applyOptions.setBoolean(VTOptionDefines.USE_NAMESPACE_FUNCTIONS, true); //replace namespace + + assertEquals(VTAssociationStatus.AVAILABLE, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.UNAPPLIED); + + List matches = new ArrayList<>(); + matches.add(testMatch); + + checkClassDataType(false, false); + + // Test Apply + ApplyMatchTask task = new ApplyMatchTask(controller, matches); + runTask(session, task); + + // Verify apply. + checkSignatures("Gadget * use(Gadget * this, Person * person)", + "Gadget * use(Gadget * this, Person * person)"); + assertEquals(VTAssociationStatus.ACCEPTED, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.REPLACED); + + // since no option to apply function name there will be no class namespace applied to + // the destination so no Gadget class data type should exist + checkClassDataType(true, false); + + // Test unapply + ClearMatchTask unapplyTask = new ClearMatchTask(controller, matches); + runTask(session, unapplyTask); + + // Verify unapply. + checkSignatures("Gadget * use(Gadget * this, Person * person)", + "undefined4 * FUN_00401040(void * this, undefined4 * param_1)"); + assertEquals(VTAssociationStatus.AVAILABLE, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.UNAPPLIED); + + } + + // Use case: Source has empty Gadget struct, dest has no Gadget struct + @Test + public void testApplyMatch_ReplaceSignature_AndName_SameNumParams_ThisToThis_NoDestGadgetStruct() + throws Exception { + + useMatch("0x00401040", "0x00401040"); + + // Check initial values + checkSignatures("undefined use(Gadget * this, Person * person)", + "undefined FUN_00401040(void * this, undefined4 param_1)"); + + // Set the function signature options for this test + ToolOptions applyOptions = controller.getOptions(); + + // function name choices - note the only difference between this and the + // testApplyMatch_ReplaceSignature_SameNumParams_ThisToThis test + // is that the option to apply the function name was added which then put the + // destination function in the Gadget namespace which then causes the this param to be + // a Gadget * + applyOptions.setEnum(VTOptionDefines.FUNCTION_NAME, + FunctionNameChoices.REPLACE_DEFAULT_ONLY); + applyOptions.setEnum(FUNCTION_SIGNATURE, + FunctionSignatureChoices.WHEN_SAME_PARAMETER_COUNT); + applyOptions.setEnum(CALLING_CONVENTION, CallingConventionChoices.SAME_LANGUAGE); + applyOptions.setEnum(PARAMETER_DATA_TYPES, ParameterDataTypeChoices.REPLACE); + applyOptions.setEnum(PARAMETER_NAMES, SourcePriorityChoices.REPLACE); + applyOptions.setEnum(PARAMETER_COMMENTS, CommentChoices.APPEND_TO_EXISTING); + applyOptions.setEnum(NO_RETURN, ReplaceChoices.EXCLUDE); + applyOptions.setEnum(FUNCTION_RETURN_TYPE, ParameterDataTypeChoices.REPLACE); + applyOptions.setBoolean(VTOptionDefines.USE_NAMESPACE_FUNCTIONS, true); //replace namespace + + assertEquals(VTAssociationStatus.AVAILABLE, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.UNAPPLIED); + + List matches = new ArrayList<>(); + matches.add(testMatch); + + checkClassDataType(false, false); + + // Test Apply + ApplyMatchTask task = new ApplyMatchTask(controller, matches); + runTask(session, task); + + // Verify apply. + checkSignatures("undefined use(Gadget * this, Person * person)", + "undefined use(Gadget * this, Person * person)"); + assertEquals(VTAssociationStatus.ACCEPTED, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.REPLACED); + + // since there is option to apply function name there should be a class namespace applied to + // the destination so the Gadget class data type should exist + // In this test Gadget is an empty structure in the source so should be empty in dest + checkClassDataType(true, false); + + // Test unapply + ClearMatchTask unapplyTask = new ClearMatchTask(controller, matches); + runTask(session, unapplyTask); + + // Verify unapply. + checkSignatures("undefined use(Gadget * this, Person * person)", + "undefined FUN_00401040(void * this, undefined4 param_1)"); + assertEquals(VTAssociationStatus.AVAILABLE, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.UNAPPLIED); + + } + + // Use case: Source has populated Gadget struct, dest has empty Gadget struct + @Test + public void testApplyMatch_ReplaceSignature_AndName_SameNumParams_ThisToThis_EmptyDestGadgetStruct() + throws Exception { + + useMatch("0x00401040", "0x00401040"); + + ProgramBasedDataTypeManager sourceDtm = sourceProgram.getDataTypeManager(); + ProgramBasedDataTypeManager destDtm = destinationProgram.getDataTypeManager(); + + DataType existingEmptyGadgetStruct = sourceDtm.getDataType(CategoryPath.ROOT, "Gadget"); + assertNotNull(existingEmptyGadgetStruct); + + // replace the source empty gadget with a populated one + Structure gadgetStruct = createGadgetStruct(); + + int id = sourceDtm.startTransaction("Test"); + sourceDtm.addDataType(gadgetStruct, DataTypeConflictHandler.REPLACE_HANDLER); + sourceDtm.endTransaction(id, true); + + // verify it took + DataType updatedSourceGadget = sourceDtm.getDataType(CategoryPath.ROOT, "Gadget"); + assertTrue(updatedSourceGadget.isEquivalent(gadgetStruct)); + + // add empty gadget to the destination program + Structure emptyGadgetStruct = createEmptyGadgetStruct(); + + id = destDtm.startTransaction("Test"); + destDtm.addDataType(emptyGadgetStruct, DataTypeConflictHandler.REPLACE_HANDLER); + destDtm.endTransaction(id, true); + + // verify it took + DataType destGadget = destDtm.getDataType(CategoryPath.ROOT, "Gadget"); + assertTrue(destGadget.isNotYetDefined()); + + // Check initial values + checkSignatures("undefined use(Gadget * this, Person * person)", + "undefined FUN_00401040(void * this, undefined4 param_1)"); + + // Set the function signature options for this test + ToolOptions applyOptions = controller.getOptions(); + + // function name choices - note the only difference between this and the + // testApplyMatch_ReplaceSignature_SameNumParams_ThisToThis test + // is that the option to apply the function name was added which then put the + // destination function in the Gadget namespace which then causes the this param to be + // a Gadget * + applyOptions.setEnum(VTOptionDefines.FUNCTION_NAME, + FunctionNameChoices.REPLACE_DEFAULT_ONLY); + applyOptions.setEnum(FUNCTION_SIGNATURE, + FunctionSignatureChoices.WHEN_SAME_PARAMETER_COUNT); + applyOptions.setEnum(CALLING_CONVENTION, CallingConventionChoices.SAME_LANGUAGE); + applyOptions.setEnum(PARAMETER_DATA_TYPES, ParameterDataTypeChoices.REPLACE); + applyOptions.setEnum(PARAMETER_NAMES, SourcePriorityChoices.REPLACE); + applyOptions.setEnum(PARAMETER_COMMENTS, CommentChoices.APPEND_TO_EXISTING); + applyOptions.setEnum(NO_RETURN, ReplaceChoices.EXCLUDE); + applyOptions.setEnum(FUNCTION_RETURN_TYPE, ParameterDataTypeChoices.REPLACE); + applyOptions.setBoolean(VTOptionDefines.USE_NAMESPACE_FUNCTIONS, true); //replace namespace + + assertEquals(VTAssociationStatus.AVAILABLE, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.UNAPPLIED); + + List matches = new ArrayList<>(); + matches.add(testMatch); + + // Test Apply + ApplyMatchTask task = new ApplyMatchTask(controller, matches); + runTask(session, task); + + // Verify apply. + checkSignatures("undefined use(Gadget * this, Person * person)", + "undefined use(Gadget * this, Person * person)"); + assertEquals(VTAssociationStatus.ACCEPTED, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.REPLACED); + + // since there is option to apply function name there should be a class namespace applied to + // the destination so the Gadget class data type should exist + // In this test Gadget is a populated structure in the source replacing an empty + // one in the dest so the resulting one in dest should be same populated on from source + destGadget = destDtm.getDataType(CategoryPath.ROOT, "Gadget"); + assertTrue(destGadget.isEquivalent(gadgetStruct)); + + // Test unapply + ClearMatchTask unapplyTask = new ClearMatchTask(controller, matches); + runTask(session, unapplyTask); + + // Verify unapply. + checkSignatures("undefined use(Gadget * this, Person * person)", + "undefined FUN_00401040(void * this, undefined4 param_1)"); + assertEquals(VTAssociationStatus.AVAILABLE, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.UNAPPLIED); + + } + + // Use case: Source has populated Gadget struct, dest has same Gadget struct + // make sure no .conflict created + @Test + public void testApplyMatch_ReplaceSignature_AndName_SameNumParams_ThisToThis_SameDestGadgetStruct() + throws Exception { + + useMatch("0x00401040", "0x00401040"); + + ProgramBasedDataTypeManager sourceDtm = sourceProgram.getDataTypeManager(); + ProgramBasedDataTypeManager destDtm = destinationProgram.getDataTypeManager(); + + DataType existingEmptyGadgetStruct = sourceDtm.getDataType(CategoryPath.ROOT, "Gadget"); + assertNotNull(existingEmptyGadgetStruct); + + // replace the source empty gadget with a populated one + Structure gadgetStruct = createGadgetStruct(); + + int id = sourceDtm.startTransaction("Test"); + sourceDtm.addDataType(gadgetStruct, DataTypeConflictHandler.REPLACE_HANDLER); + sourceDtm.endTransaction(id, true); + + // verify it took + DataType updatedSourceGadget = sourceDtm.getDataType(CategoryPath.ROOT, "Gadget"); + assertTrue(updatedSourceGadget.isEquivalent(gadgetStruct)); + + // add same Gadget to the destination program + + id = destDtm.startTransaction("Test"); + destDtm.addDataType(gadgetStruct, DataTypeConflictHandler.REPLACE_HANDLER); + destDtm.endTransaction(id, true); + + // verify it took + DataType destGadget = destDtm.getDataType(CategoryPath.ROOT, "Gadget"); + assertTrue(destGadget.isEquivalent(gadgetStruct)); + + // Check initial values + checkSignatures("undefined use(Gadget * this, Person * person)", + "undefined FUN_00401040(void * this, undefined4 param_1)"); + + // Set the function signature options for this test + ToolOptions applyOptions = controller.getOptions(); + + // function name choices - note the only difference between this and the + // testApplyMatch_ReplaceSignature_SameNumParams_ThisToThis test + // is that the option to apply the function name was added which then put the + // destination function in the Gadget namespace which then causes the this param to be + // a Gadget * + applyOptions.setEnum(VTOptionDefines.FUNCTION_NAME, + FunctionNameChoices.REPLACE_DEFAULT_ONLY); + applyOptions.setEnum(FUNCTION_SIGNATURE, + FunctionSignatureChoices.WHEN_SAME_PARAMETER_COUNT); + applyOptions.setEnum(CALLING_CONVENTION, CallingConventionChoices.SAME_LANGUAGE); + applyOptions.setEnum(PARAMETER_DATA_TYPES, ParameterDataTypeChoices.REPLACE); + applyOptions.setEnum(PARAMETER_NAMES, SourcePriorityChoices.REPLACE); + applyOptions.setEnum(PARAMETER_COMMENTS, CommentChoices.APPEND_TO_EXISTING); + applyOptions.setEnum(NO_RETURN, ReplaceChoices.EXCLUDE); + applyOptions.setEnum(FUNCTION_RETURN_TYPE, ParameterDataTypeChoices.REPLACE); + applyOptions.setBoolean(VTOptionDefines.USE_NAMESPACE_FUNCTIONS, true); //replace namespace + + assertEquals(VTAssociationStatus.AVAILABLE, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.UNAPPLIED); + + List matches = new ArrayList<>(); + matches.add(testMatch); + + // Test Apply + ApplyMatchTask task = new ApplyMatchTask(controller, matches); + runTask(session, task); + + // Verify apply. + checkSignatures("undefined use(Gadget * this, Person * person)", + "undefined use(Gadget * this, Person * person)"); + assertEquals(VTAssociationStatus.ACCEPTED, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.REPLACED); + + // since there is option to apply function name there should be a class namespace applied to + // the destination so the Gadget class data type should exist + // In this test Gadget is a populated structure in the source and the same one in + // one in the dest so the resulting one in dest should be same populated on from source + destGadget = destDtm.getDataType(CategoryPath.ROOT, "Gadget"); + assertTrue(destGadget.isEquivalent(gadgetStruct)); + + DataType gadgetConflict = destDtm.getDataType(CategoryPath.ROOT, "Gadget.conflict"); + assertNull(gadgetConflict); + + // Test unapply + ClearMatchTask unapplyTask = new ClearMatchTask(controller, matches); + runTask(session, unapplyTask); + + // Verify unapply. + checkSignatures("undefined use(Gadget * this, Person * person)", + "undefined FUN_00401040(void * this, undefined4 param_1)"); + assertEquals(VTAssociationStatus.AVAILABLE, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.UNAPPLIED); + + } + + // Use case: Source has populated Gadget struct, dest has different non-empty Gadget struct + // make sure .conflict IS created + @Test + public void testApplyMatch_ReplaceSignature_AndName_SameNumParams_ThisToThis_DiffDestGadgetStruct() + throws Exception { + + useMatch("0x00401040", "0x00401040"); + + ProgramBasedDataTypeManager sourceDtm = sourceProgram.getDataTypeManager(); + ProgramBasedDataTypeManager destDtm = destinationProgram.getDataTypeManager(); + + DataType existingEmptyGadgetStruct = sourceDtm.getDataType(CategoryPath.ROOT, "Gadget"); + assertNotNull(existingEmptyGadgetStruct); + + // replace the source empty gadget with a populated one + Structure gadgetStruct = createGadgetStruct(); + + int id = sourceDtm.startTransaction("Test"); + sourceDtm.addDataType(gadgetStruct, DataTypeConflictHandler.REPLACE_HANDLER); + sourceDtm.endTransaction(id, true); + + // verify it took + DataType updatedSourceGadget = sourceDtm.getDataType(CategoryPath.ROOT, "Gadget"); + assertTrue(updatedSourceGadget.isEquivalent(gadgetStruct)); + + // add different Gadget to the destination program + Structure differentGadgetStruct = createDifferentGadgetStruct(); + id = destDtm.startTransaction("Test"); + destDtm.addDataType(differentGadgetStruct, DataTypeConflictHandler.REPLACE_HANDLER); + destDtm.endTransaction(id, true); + + // verify it took + DataType destGadget = destDtm.getDataType(CategoryPath.ROOT, "Gadget"); + assertTrue(destGadget.isEquivalent(differentGadgetStruct)); + + // Check initial values + checkSignatures("undefined use(Gadget * this, Person * person)", + "undefined FUN_00401040(void * this, undefined4 param_1)"); + + // Set the function signature options for this test + ToolOptions applyOptions = controller.getOptions(); + + // function name choices - note the only difference between this and the + // testApplyMatch_ReplaceSignature_SameNumParams_ThisToThis test + // is that the option to apply the function name was added which then put the + // destination function in the Gadget namespace which then causes the this param to be + // a Gadget * + applyOptions.setEnum(VTOptionDefines.FUNCTION_NAME, + FunctionNameChoices.REPLACE_DEFAULT_ONLY); + applyOptions.setEnum(FUNCTION_SIGNATURE, + FunctionSignatureChoices.WHEN_SAME_PARAMETER_COUNT); + applyOptions.setEnum(CALLING_CONVENTION, CallingConventionChoices.SAME_LANGUAGE); + applyOptions.setEnum(PARAMETER_DATA_TYPES, ParameterDataTypeChoices.REPLACE); + applyOptions.setEnum(PARAMETER_NAMES, SourcePriorityChoices.REPLACE); + applyOptions.setEnum(PARAMETER_COMMENTS, CommentChoices.APPEND_TO_EXISTING); + applyOptions.setEnum(NO_RETURN, ReplaceChoices.EXCLUDE); + applyOptions.setEnum(FUNCTION_RETURN_TYPE, ParameterDataTypeChoices.REPLACE); + applyOptions.setBoolean(VTOptionDefines.USE_NAMESPACE_FUNCTIONS, true); //replace namespace + + assertEquals(VTAssociationStatus.AVAILABLE, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.UNAPPLIED); + + List matches = new ArrayList<>(); + matches.add(testMatch); + + // Test Apply + ApplyMatchTask task = new ApplyMatchTask(controller, matches); + runTask(session, task); + + // Verify apply. + checkSignatures("undefined use(Gadget * this, Person * person)", + "undefined use(Gadget * this, Person * person)"); + assertEquals(VTAssociationStatus.ACCEPTED, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.REPLACED); + + // since there is option to apply function name there should be a class namespace applied to + // the destination so the Gadget class data type should exist + // In this test Gadget is a populated structure in the source replacing a different non-empty + // one in the dest so the resulting one in dest should be the source one with the previous + // source one named .conflict + destGadget = destDtm.getDataType(CategoryPath.ROOT, "Gadget"); + assertTrue(destGadget.isEquivalent(differentGadgetStruct)); + + DataType gadgetConflict = destDtm.getDataType(CategoryPath.ROOT, "Gadget.conflict"); + assertTrue(gadgetConflict.isEquivalent(gadgetStruct)); + + // Test unapply + ClearMatchTask unapplyTask = new ClearMatchTask(controller, matches); + runTask(session, unapplyTask); + + // Verify unapply. + checkSignatures("undefined use(Gadget * this, Person * person)", + "undefined FUN_00401040(void * this, undefined4 param_1)"); + assertEquals(VTAssociationStatus.AVAILABLE, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.UNAPPLIED); + } @Test @@ -379,6 +878,8 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg List matches = new ArrayList<>(); matches.add(testMatch); + checkClassDataType(false, false); + // Test Apply ApplyMatchTask task = new ApplyMatchTask(controller, matches); runTask(session, task); @@ -389,6 +890,10 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg assertEquals(VTAssociationStatus.ACCEPTED, testMatch.getAssociation().getStatus()); checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.REPLACED); + // since there is custom storage in this case the this param will be copied over even + // though the function isn't in the class namespace so the class data type should exist + checkClassDataType(true, true); + assertTrue(destinationFunction.hasCustomVariableStorage()); // Test unapply @@ -405,6 +910,83 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg assertFalse(destinationFunction.hasCustomVariableStorage()); } + @Test + public void testApplyMatch_ReplaceSignature_AndName_CustomSourceNormalDest_SameNumParams_ThisToThis() + throws Exception { + useMatch("0x00401040", "0x00401040"); + + // Check initial values + checkSignatures("undefined use(Gadget * this, Person * person)", + "undefined FUN_00401040(void * this, undefined4 param_1)"); + + tx(sourceProgram, () -> { + sourceFunction.setCustomVariableStorage(true); + + sourceFunction.getParameter(0) + .setDataType(sourceFunction.getParameter(1).getDataType(), + SourceType.USER_DEFINED); + }); + + DataType personType = sourceProgram.getDataTypeManager().getDataType("/Person"); + assertNotNull(personType); + + tx(destinationProgram, () -> { + destinationFunction.setReturnType(personType, SourceType.USER_DEFINED); + }); + + // Check modified values + checkSignatures("undefined use(Person * this, Person * person)", + "Person * FUN_00401040(void * this, Person * __return_storage_ptr__, undefined4 param_1)"); + + // Set the function signature options for this test + ToolOptions applyOptions = controller.getOptions(); + applyOptions.setEnum(VTOptionDefines.FUNCTION_NAME, + FunctionNameChoices.REPLACE_DEFAULT_ONLY); + applyOptions.setEnum(FUNCTION_SIGNATURE, FunctionSignatureChoices.REPLACE); + applyOptions.setEnum(CALLING_CONVENTION, CallingConventionChoices.SAME_LANGUAGE); + applyOptions.setEnum(PARAMETER_DATA_TYPES, ParameterDataTypeChoices.REPLACE); + applyOptions.setEnum(PARAMETER_NAMES, SourcePriorityChoices.REPLACE); + applyOptions.setEnum(PARAMETER_COMMENTS, CommentChoices.APPEND_TO_EXISTING); + applyOptions.setEnum(NO_RETURN, ReplaceChoices.EXCLUDE); + applyOptions.setEnum(FUNCTION_RETURN_TYPE, ParameterDataTypeChoices.REPLACE); + + assertEquals(VTAssociationStatus.AVAILABLE, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.UNAPPLIED); + + List matches = new ArrayList<>(); + matches.add(testMatch); + checkClassDataType(false, false); + + // Test Apply + ApplyMatchTask task = new ApplyMatchTask(controller, matches); + runTask(session, task); + + // Verify apply. (return type not replaced with undefined due to lower priority) + checkSignatures("undefined use(Person * this, Person * person)", + "Person * use(Person * this, Person * person)"); + assertEquals(VTAssociationStatus.ACCEPTED, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.REPLACED); + + assertTrue(destinationFunction.hasCustomVariableStorage()); + + // since there is option to apply function name there should be a class namespace applied to + // the destination so the Gadget class data type should exist + checkClassDataType(true, true); + + // Test unapply + ClearMatchTask unapplyTask = new ClearMatchTask(controller, matches); + runTask(session, unapplyTask); + + // Verify unapply. + checkSignatures("undefined use(Person * this, Person * person)", + "Person * FUN_00401040(void * this, Person * __return_storage_ptr__, undefined4 param_1)"); + + assertEquals(VTAssociationStatus.AVAILABLE, testMatch.getAssociation().getStatus()); + checkFunctionSignatureStatus(testMatch, VTMarkupItemStatus.UNAPPLIED); + + assertFalse(destinationFunction.hasCustomVariableStorage()); + } + @Test public void testApplyMatch_ReplaceSignature_CustomSourceAndDest() throws Exception { @@ -1698,6 +2280,39 @@ public class VTMatchApplyFunctionSignatureTest extends AbstractGhidraHeadedInteg expectedStatus, vtMarkupItem.getStatus()); } + private void checkClassDataType(boolean shouldExist, boolean shouldBeNonEmpty) { + + Parameter srcParam = sourceFunction.getParameter(0); + DataTypePath dataTypePath = srcParam.getDataType().getDataTypePath(); + + ProgramBasedDataTypeManager dstDtman = destinationProgram.getDataTypeManager(); + DataType dstDataType = dstDtman.getDataType(dataTypePath); + + if (shouldExist) { + assertTrue(dstDataType instanceof Pointer); + + Pointer dstDataTypePtr = (Pointer) dstDataType; + + DataType pointedToDataType = dstDataTypePtr.getDataType(); + + assertTrue(pointedToDataType instanceof Structure); + + Structure struct = (Structure) pointedToDataType; + + assertNotNull(struct); + if (shouldBeNonEmpty) { + assertFalse(struct.isNotYetDefined()); + } + else { + assertTrue(struct.isNotYetDefined()); + } + } + else { + assertNull(dstDataType); + } + + } + private VTMarkupItem getFunctionSignatureMarkup(VTMatch match) { MatchInfo matchInfo = controller.getMatchInfo(match); Collection appliableMarkupItems =