GP-1464 RecoverClassesFromRTTIScript now consistently applies its class structures in programs that have PDB information applied. Also, an option was added so users can decide whether to replace existing class data in thiscall functions regardless of whether they originated as PDB or not.

This commit is contained in:
ghidra007 2021-11-10 19:59:21 +00:00
parent 2c561c875b
commit 0766c30048
7 changed files with 207 additions and 36 deletions

View file

@ -45,7 +45,7 @@ public class ApplyClassFunctionDefinitionUpdatesScript extends GhidraScript {
} }
RecoveredClassUtils classUtils = new RecoveredClassUtils(currentProgram, currentLocation, RecoveredClassUtils classUtils = new RecoveredClassUtils(currentProgram, currentLocation,
state.getTool(), this, false, false, false, monitor); state.getTool(), this, false, false, false, false, monitor);
Namespace classNamespace = classUtils.getClassNamespace(currentAddress); Namespace classNamespace = classUtils.getClassNamespace(currentAddress);
if (classNamespace == null) { if (classNamespace == null) {

View file

@ -45,7 +45,7 @@ public class ApplyClassFunctionSignatureUpdatesScript extends GhidraScript {
} }
RecoveredClassUtils classUtils = new RecoveredClassUtils(currentProgram, currentLocation, RecoveredClassUtils classUtils = new RecoveredClassUtils(currentProgram, currentLocation,
state.getTool(), this, false, false, false, monitor); state.getTool(), this, false, false, false, false, monitor);
Namespace classNamespace = classUtils.getClassNamespace(currentAddress); Namespace classNamespace = classUtils.getClassNamespace(currentAddress);
if (classNamespace == null) { if (classNamespace == null) {

View file

@ -121,6 +121,12 @@ public class RecoverClassesFromRTTIScript extends GhidraScript {
// show shortened class template names in class structure field names // show shortened class template names in class structure field names
private static final boolean USE_SHORT_TEMPLATE_NAMES_IN_STRUCTURE_FIELDS = true; private static final boolean USE_SHORT_TEMPLATE_NAMES_IN_STRUCTURE_FIELDS = true;
// replace defined existing class structures (ie pdb, fid, demangler, or other)with ones created by
// this script and rename the existing ones with a _REPLACED suffix
// NOTE: currently does not replace DWARF
// NEW OPTION:
private static final boolean REPLACE_EXISTING_CLASS_STRUCTURES = true;
private static final String CLASS_DATA_STRUCT_NAME = "_data"; private static final String CLASS_DATA_STRUCT_NAME = "_data";
private static final String CONSTRUCTOR_BOOKMARK = "CONSTRUCTOR"; private static final String CONSTRUCTOR_BOOKMARK = "CONSTRUCTOR";
@ -162,6 +168,7 @@ public class RecoverClassesFromRTTIScript extends GhidraScript {
recoverClassesFromRTTI = new RTTIWindowsClassRecoverer(currentProgram, recoverClassesFromRTTI = new RTTIWindowsClassRecoverer(currentProgram,
currentLocation, state.getTool(), this, BOOKMARK_FOUND_FUNCTIONS, currentLocation, state.getTool(), this, BOOKMARK_FOUND_FUNCTIONS,
USE_SHORT_TEMPLATE_NAMES_IN_STRUCTURE_FIELDS, nameVfunctions, hasDebugSymbols, USE_SHORT_TEMPLATE_NAMES_IN_STRUCTURE_FIELDS, nameVfunctions, hasDebugSymbols,
REPLACE_EXISTING_CLASS_STRUCTURES,
monitor); monitor);
} }
else if (isGcc()) { else if (isGcc()) {
@ -182,6 +189,7 @@ public class RecoverClassesFromRTTIScript extends GhidraScript {
recoverClassesFromRTTI = new RTTIGccClassRecoverer(currentProgram, currentLocation, recoverClassesFromRTTI = new RTTIGccClassRecoverer(currentProgram, currentLocation,
state.getTool(), this, BOOKMARK_FOUND_FUNCTIONS, state.getTool(), this, BOOKMARK_FOUND_FUNCTIONS,
USE_SHORT_TEMPLATE_NAMES_IN_STRUCTURE_FIELDS, nameVfunctions, hasDebugSymbols, USE_SHORT_TEMPLATE_NAMES_IN_STRUCTURE_FIELDS, nameVfunctions, hasDebugSymbols,
REPLACE_EXISTING_CLASS_STRUCTURES,
monitor); monitor);
} }
else { else {

View file

@ -40,12 +40,14 @@ public class RTTIClassRecoverer extends RecoveredClassUtils {
TaskMonitor monitor; TaskMonitor monitor;
boolean hasDebugSymbols; boolean hasDebugSymbols;
RTTIClassRecoverer(Program program, ProgramLocation location, PluginTool tool, RTTIClassRecoverer(Program program, ProgramLocation location, PluginTool tool,
FlatProgramAPI api, boolean createBookmarks, boolean useShortTemplates, FlatProgramAPI api, boolean createBookmarks, boolean useShortTemplates,
boolean nameVfunctions, boolean hasDebugSymbols, boolean nameVfunctions, boolean hasDebugSymbols, boolean replaceClassStructures,
TaskMonitor monitor) { TaskMonitor monitor) {
super(program, location, tool, api, createBookmarks, useShortTemplates, nameVfunctions, super(program, location, tool, api, createBookmarks, useShortTemplates, nameVfunctions,
replaceClassStructures,
monitor); monitor);
this.program = program; this.program = program;

View file

@ -69,15 +69,19 @@ public class RTTIGccClassRecoverer extends RTTIClassRecoverer {
new HashMap<RecoveredClass, Map<RecoveredClass, Long>>(); new HashMap<RecoveredClass, Map<RecoveredClass, Long>>();
boolean isDwarfLoaded; boolean isDwarfLoaded;
boolean replaceClassStructs;
public RTTIGccClassRecoverer(Program program, ProgramLocation location, PluginTool tool, public RTTIGccClassRecoverer(Program program, ProgramLocation location, PluginTool tool,
FlatProgramAPI api, boolean createBookmarks, boolean useShortTemplates, FlatProgramAPI api, boolean createBookmarks, boolean useShortTemplates,
boolean nameVfunctions, boolean isDwarfLoaded, TaskMonitor monitor) { boolean nameVfunctions, boolean isDwarfLoaded, boolean replaceExistingClassStructures,
TaskMonitor monitor) {
super(program, location, tool, api, createBookmarks, useShortTemplates, nameVfunctions, super(program, location, tool, api, createBookmarks, useShortTemplates, nameVfunctions,
replaceExistingClassStructures,
isDwarfLoaded, isDwarfLoaded,
monitor); monitor);
this.isDwarfLoaded = isDwarfLoaded; this.isDwarfLoaded = isDwarfLoaded;
this.replaceClassStructs = replaceExistingClassStructures;
} }
@Override @Override
@ -2898,12 +2902,14 @@ public class RTTIGccClassRecoverer extends RTTIClassRecoverer {
Structure classStruct = createSimpleClassStructure(recoveredClass, vfPointerDataTypes); Structure classStruct = createSimpleClassStructure(recoveredClass, vfPointerDataTypes);
// check for DWARF -- if none add c/d/etc to class // check for DWARF -- if none add c/d/etc to class
//TODO: if decide to replace dwarf data types then remove this check so the replaces
// in the following methods can replace the dwarf data types
if (!isDwarfLoaded) { if (!isDwarfLoaded) {
// Now that we have a class data type // Now that we have a class data type
// name constructor and destructor functions and put into the class namespace // name constructor and destructor functions and put into the class namespace
addConstructorsToClassNamespace(recoveredClass, classStruct); addConstructorsToClassNamespace(recoveredClass, classStruct);
addDestructorsToClassNamespace(recoveredClass); addDestructorsToClassNamespace(recoveredClass, classStruct);
// addNonThisDestructorsToClassNamespace(recoveredClass); // addNonThisDestructorsToClassNamespace(recoveredClass);
// addVbaseDestructorsToClassNamespace(recoveredClass); // addVbaseDestructorsToClassNamespace(recoveredClass);
// addVbtableToClassNamespace(recoveredClass); // addVbtableToClassNamespace(recoveredClass);
@ -2914,7 +2920,7 @@ public class RTTIGccClassRecoverer extends RTTIClassRecoverer {
// createIndeterminateInlineComments(recoveredClass); // createIndeterminateInlineComments(recoveredClass);
// add label on constructor destructor functions that could not be determined which were which // add label on constructor destructor functions that could not be determined which were which
createIndeterminateLabels(recoveredClass); createIndeterminateLabels(recoveredClass, classStruct);
} }
// This is done after the class structure is created and added to the dtmanager // This is done after the class structure is created and added to the dtmanager
@ -2922,7 +2928,8 @@ public class RTTIGccClassRecoverer extends RTTIClassRecoverer {
// then empty classes will get auto-created in the wrong place // then empty classes will get auto-created in the wrong place
// when the vfunctions are put in the class // when the vfunctions are put in the class
fillInAndApplyVftableStructAndNameVfunctions(recoveredClass, vfPointerDataTypes); fillInAndApplyVftableStructAndNameVfunctions(recoveredClass, vfPointerDataTypes,
classStruct);
} }

View file

@ -69,11 +69,11 @@ public class RTTIWindowsClassRecoverer extends RTTIClassRecoverer {
public RTTIWindowsClassRecoverer(Program program, ProgramLocation location, PluginTool tool, public RTTIWindowsClassRecoverer(Program program, ProgramLocation location, PluginTool tool,
FlatProgramAPI api, boolean createBookmarks, boolean useShortTemplates, FlatProgramAPI api, boolean createBookmarks, boolean useShortTemplates,
boolean nameVFunctions, boolean isPDBLoaded, boolean nameVFunctions, boolean isPDBLoaded, boolean replaceClassStructures,
TaskMonitor monitor) throws CancelledException { TaskMonitor monitor) throws CancelledException {
super(program, location, tool, api, createBookmarks, useShortTemplates, nameVFunctions, super(program, location, tool, api, createBookmarks, useShortTemplates, nameVFunctions,
isPDBLoaded, monitor); isPDBLoaded, replaceClassStructures, monitor);
this.isPDBLoaded = isPDBLoaded; this.isPDBLoaded = isPDBLoaded;
@ -2245,32 +2245,35 @@ public class RTTIWindowsClassRecoverer extends RTTIClassRecoverer {
applyVbtableStructure(recoveredClass); applyVbtableStructure(recoveredClass);
// pdb already has good names so only name if no pdb
if (!isPDBLoaded) {
// Now that we have a class data type // Now that we have a class data type
// name constructor and destructor functions and put into the class namespace // name constructor and destructor functions and put into the class namespace
// checks are internal for hasDebugSymbols since there
// are also replace methods that need to be called either way
addConstructorsToClassNamespace(recoveredClass, classStruct); addConstructorsToClassNamespace(recoveredClass, classStruct);
addDestructorsToClassNamespace(recoveredClass); addDestructorsToClassNamespace(recoveredClass, classStruct);
addVbaseDestructorsToClassNamespace(recoveredClass, classStruct);
if (!hasDebugSymbols) {
addNonThisDestructorsToClassNamespace(recoveredClass); addNonThisDestructorsToClassNamespace(recoveredClass);
addVbaseDestructorsToClassNamespace(recoveredClass);
addVbtableToClassNamespace(recoveredClass); addVbtableToClassNamespace(recoveredClass);
// add secondary label on functions with inlined constructors or destructors // add secondary label on functions with inlined constructors or destructors
createInlinedConstructorComments(recoveredClass); createInlinedConstructorComments(recoveredClass);
createInlinedDestructorComments(recoveredClass); createInlinedDestructorComments(recoveredClass);
createIndeterminateInlineComments(recoveredClass); createIndeterminateInlineComments(recoveredClass);
}
// add label on constructor destructor functions that could not be determined which were which // add label on constructor destructor functions that could not be determined which were which
createIndeterminateLabels(recoveredClass); createIndeterminateLabels(recoveredClass, classStruct);
}
// This is done after the class structure is created and added to the dtmanager // This is done after the class structure is created and added to the dtmanager
// because if done before the class structures are created // because if done before the class structures are created
// then empty classes will get auto-created in the wrong place // then empty classes will get auto-created in the wrong place
// when the vfunctions are put in the class // when the vfunctions are put in the class
fillInAndApplyVftableStructAndNameVfunctions(recoveredClass, vfPointerDataTypes,
fillInAndApplyVftableStructAndNameVfunctions(recoveredClass, vfPointerDataTypes); classStruct);
} }

View file

@ -144,10 +144,11 @@ public class RecoveredClassUtils {
boolean createBookmarks; boolean createBookmarks;
boolean useShortTemplates; boolean useShortTemplates;
boolean nameVfunctions; boolean nameVfunctions;
boolean replaceClassStructures;
public RecoveredClassUtils(Program program, ProgramLocation location, PluginTool tool, public RecoveredClassUtils(Program program, ProgramLocation location, PluginTool tool,
FlatProgramAPI api, boolean createBookmarks, boolean useShortTemplates, FlatProgramAPI api, boolean createBookmarks, boolean useShortTemplates,
boolean nameVfunctions, TaskMonitor monitor) { boolean nameVunctions, boolean replaceClassStructures, TaskMonitor monitor) {
this.monitor = monitor; this.monitor = monitor;
this.program = program; this.program = program;
@ -162,7 +163,8 @@ public class RecoveredClassUtils {
this.createBookmarks = createBookmarks; this.createBookmarks = createBookmarks;
this.useShortTemplates = useShortTemplates; this.useShortTemplates = useShortTemplates;
this.nameVfunctions = nameVfunctions; this.nameVfunctions = nameVunctions;
this.replaceClassStructures = replaceClassStructures;
globalNamespace = (GlobalNamespace) program.getGlobalNamespace(); globalNamespace = (GlobalNamespace) program.getGlobalNamespace();
@ -3294,12 +3296,17 @@ public class RecoveredClassUtils {
monitor.checkCanceled(); monitor.checkCanceled();
Function constructorFunction = constructorsIterator.next(); Function constructorFunction = constructorsIterator.next();
createNewSymbolAtFunction(constructorFunction, className, classNamespace, true, true); if (nameVfunctions) {
createNewSymbolAtFunction(constructorFunction, className, classNamespace, true,
true);
}
// check to see if the "this" data type is an empty placeholder for the class // check to see if the "this" data type is an empty placeholder for the class
// structure and replace it with the one that was just created by the script // structure and replace it with the one that was just created by the script
//deleteEmptyClassStructure(constructorFunction, className); //NEW
replaceEmptyClassStructure(constructorFunction, className, classStruct); if (replaceClassStructures) {
replaceClassStructure(constructorFunction, className, classStruct);
}
// if current decompiler function return type is a pointer then set the return type // if current decompiler function return type is a pointer then set the return type
// to a pointer to the class structure, otherwise if it is a void, make it a void so the // to a pointer to the class structure, otherwise if it is a void, make it a void so the
@ -3337,9 +3344,11 @@ public class RecoveredClassUtils {
/** /**
* Method to name class destructors and add them to class namespace * Method to name class destructors and add them to class namespace
* @param recoveredClass current class * @param recoveredClass current class
* @param classStruct the class structure for the given class
* @throws Exception when cancelled * @throws Exception when cancelled
*/ */
public void addDestructorsToClassNamespace(RecoveredClass recoveredClass) throws Exception { public void addDestructorsToClassNamespace(RecoveredClass recoveredClass, Structure classStruct)
throws Exception {
Namespace classNamespace = recoveredClass.getClassNamespace(); Namespace classNamespace = recoveredClass.getClassNamespace();
String className = recoveredClass.getName(); String className = recoveredClass.getName();
@ -3351,8 +3360,17 @@ public class RecoveredClassUtils {
Function destructorFunction = destructorIterator.next(); Function destructorFunction = destructorIterator.next();
String destructorName = "~" + className; String destructorName = "~" + className;
if (nameVfunctions) {
createNewSymbolAtFunction(destructorFunction, destructorName, classNamespace, true, createNewSymbolAtFunction(destructorFunction, destructorName, classNamespace, true,
true); true);
}
// check to see if the "this" data type is an empty placeholder for the class
// structure and replace it with the one that was just created by the script
//NEW
if (replaceClassStructures) {
replaceClassStructure(destructorFunction, className, classStruct);
}
destructorFunction.setReturnType(DataType.VOID, SourceType.ANALYSIS); destructorFunction.setReturnType(DataType.VOID, SourceType.ANALYSIS);
} }
@ -3384,9 +3402,11 @@ public class RecoveredClassUtils {
/** /**
* Method to name class vbase destructors and add them to class namespace * Method to name class vbase destructors and add them to class namespace
* @param recoveredClass current class * @param recoveredClass current class
* @param classStruct the class structure for the given class
* @throws Exception when cancelled * @throws Exception when cancelled
*/ */
public void addVbaseDestructorsToClassNamespace(RecoveredClass recoveredClass) public void addVbaseDestructorsToClassNamespace(RecoveredClass recoveredClass,
Structure classStruct)
throws Exception { throws Exception {
Namespace classNamespace = recoveredClass.getClassNamespace(); Namespace classNamespace = recoveredClass.getClassNamespace();
@ -3395,8 +3415,18 @@ public class RecoveredClassUtils {
if (vbaseDestructorFunction != null) { if (vbaseDestructorFunction != null) {
String destructorName = VBASE_DESTRUCTOR_LABEL; String destructorName = VBASE_DESTRUCTOR_LABEL;
createNewSymbolAtFunction(vbaseDestructorFunction, destructorName, classNamespace, true, if (nameVfunctions) {
true); createNewSymbolAtFunction(vbaseDestructorFunction, destructorName, classNamespace,
true, true);
}
// check to see if the "this" data type is an empty placeholder for the class
// structure and replace it with the one that was just created by the script
//NEW
if (replaceClassStructures) {
replaceClassStructure(vbaseDestructorFunction, recoveredClass.getName(),
classStruct);
}
vbaseDestructorFunction.setReturnType(DataType.VOID, SourceType.ANALYSIS); vbaseDestructorFunction.setReturnType(DataType.VOID, SourceType.ANALYSIS);
} }
@ -3505,6 +3535,106 @@ public class RecoveredClassUtils {
} }
} }
/**
* Method to replace the program's current class structure, only if an empty placeholder structure,
* with the one generated by this script
* @param function a class method with current class structure applied
* @param className the given class name
* @param newClassStructure the new structure to replace the old with
* @throws DataTypeDependencyException if there is a data dependency exception when replacing
* @throws CancelledException if cancelled
*/
public void replaceClassStructure(Function function, String className,
Structure newClassStructure) throws DataTypeDependencyException, CancelledException {
Parameter thisParam = function.getParameter(0);
if (thisParam == null) {
return;
}
DataType dataType = thisParam.getDataType();
if (dataType instanceof Pointer) {
Pointer ptr = (Pointer) dataType;
DataType baseDataType = ptr.getDataType();
if (!baseDataType.equals(newClassStructure) &&
baseDataType.getName().equals(className)) {
// check if fid demangler or pdb - don't replace user ones
if (!isReplaceableType(function.getEntryPoint(), baseDataType)) {
return;
}
// create copy of existing one
DataType baseDataTypeCopy = baseDataType.copy(dataTypeManager);
renameDataType(baseDataTypeCopy, baseDataType.getName() + "_REPLACED");
// replace the other with the new one
dataTypeManager.replaceDataType(baseDataType, newClassStructure, false);
// // remove original folder if it is empty after the replace
// in future if decide to just remove the other ones, then do the following
// CategoryPath originalPath = baseDataType.getCategoryPath();
// Category category = dataTypeManager.getCategory(originalPath);
// Category parentCategory = category.getParent();
// if (parentCategory != null) {
// parentCategory.removeEmptyCategory(category.getName(), monitor);
// }
}
}
}
private void renameDataType(DataType dataType, String name) throws CancelledException {
boolean renamed = false;
int oneup = 2;
while (!renamed) {
monitor.checkCanceled();
try {
dataType.setName(name);
dataTypeManager.resolve(dataType, DataTypeConflictHandler.DEFAULT_HANDLER);
renamed = true;
}
catch (InvalidNameException | DuplicateNameException e) {
name = name + oneup++;
renamed = false;
}
}
}
private boolean isReplaceableType(Address address, DataType dataType) {
// return false if it isn't even a structure
if (!(dataType instanceof Structure)) {
return false;
}
String categoryPath = dataType.getPathName();
if (categoryPath.startsWith("/Demangler")) {
return true;
}
if (categoryPath.contains(".pdb")) {
return true;
}
//TODO: decide whether to replace dwarf or not
// test to see if the data type is an empty structure with "PlaceHolder Class Structure" in
// the description
Structure structure = (Structure) dataType;
if (structure.isNotYetDefined() &&
structure.getDescription().equals("PlaceHolder Class Structure")) {
return true;
}
if (program.getBookmarkManager().getBookmark(address, BookmarkType.ANALYSIS,
"Function ID Analyzer") != null) {
return true;
}
return false;
}
/** /**
* Method to create a new symbol at the given function * Method to create a new symbol at the given function
* @param function the given function * @param function the given function
@ -4435,11 +4565,12 @@ public class RecoveredClassUtils {
* Method to fill in the vftable structure with pointers to virtual function signature data types * Method to fill in the vftable structure with pointers to virtual function signature data types
* @param recoveredClass the current class to be processed * @param recoveredClass the current class to be processed
* @param vftableToStructureMap the map from the class's vftables to the correct vftable structure data type * @param vftableToStructureMap the map from the class's vftables to the correct vftable structure data type
* @param classStruct the class structure for the given class
* @throws CancelledException when cancelled * @throws CancelledException when cancelled
* @throws Exception if other exception * @throws Exception if other exception
*/ */
public void fillInAndApplyVftableStructAndNameVfunctions(RecoveredClass recoveredClass, public void fillInAndApplyVftableStructAndNameVfunctions(RecoveredClass recoveredClass,
Map<Address, DataType> vftableToStructureMap) throws CancelledException, Exception { Map<Address, DataType> vftableToStructureMap, Structure classStruct) throws CancelledException, Exception {
//create function definition for each virtual function and put in vftable structure and //create function definition for each virtual function and put in vftable structure and
// data subfolder // data subfolder
@ -4468,6 +4599,7 @@ public class RecoveredClassUtils {
nameVfunctions(recoveredClass, vftableAddress, vftableStructureName); nameVfunctions(recoveredClass, vftableAddress, vftableStructureName);
} }
List<Function> vFunctions = recoveredClass.getVirtualFunctions(vftableAddress); List<Function> vFunctions = recoveredClass.getVirtualFunctions(vftableAddress);
int vfunctionNumber = 1; int vfunctionNumber = 1;
Iterator<Function> vfIterator = vFunctions.iterator(); Iterator<Function> vfIterator = vFunctions.iterator();
@ -4483,6 +4615,13 @@ public class RecoveredClassUtils {
continue; continue;
} }
// check to see if the "this" data type is an empty placeholder for the class
// structure and replace it with the one that was just created by the script
//NEW
if (replaceClassStructures) {
replaceClassStructure(vfunction, recoveredClass.getName(), classStruct);
}
// get the classPath of highest level parent with vfAddress in their vftable // get the classPath of highest level parent with vfAddress in their vftable
classPath = classPath =
getCategoryPathForFunctionSignature(vfunction, recoveredClass, vftableAddress); getCategoryPathForFunctionSignature(vfunction, recoveredClass, vftableAddress);
@ -5008,9 +5147,11 @@ public class RecoveredClassUtils {
/** /**
* Method to add label on constructor or destructors but couldn't tell which * Method to add label on constructor or destructors but couldn't tell which
* @param recoveredClass current class * @param recoveredClass current class
* @param classStruct the class structure for the given class
* @throws Exception when cancelled * @throws Exception when cancelled
*/ */
public void createIndeterminateLabels(RecoveredClass recoveredClass) throws Exception { public void createIndeterminateLabels(RecoveredClass recoveredClass, Structure classStruct)
throws Exception {
Namespace classNamespace = recoveredClass.getClassNamespace(); Namespace classNamespace = recoveredClass.getClassNamespace();
String className = recoveredClass.getName(); String className = recoveredClass.getName();
@ -5020,9 +5161,19 @@ public class RecoveredClassUtils {
while (unknownsIterator.hasNext()) { while (unknownsIterator.hasNext()) {
monitor.checkCanceled(); monitor.checkCanceled();
Function indeterminateFunction = unknownsIterator.next(); Function indeterminateFunction = unknownsIterator.next();
if (nameVfunctions) {
createNewSymbolAtFunction(indeterminateFunction, createNewSymbolAtFunction(indeterminateFunction,
className + "_Constructor_or_Destructor", classNamespace, false, false); className + "_Constructor_or_Destructor", classNamespace, false, false);
} }
// check to see if the "this" data type is an empty placeholder for the class
// structure and replace it with the one that was just created by the script
//NEW
if (replaceClassStructures) {
replaceClassStructure(indeterminateFunction, className, classStruct);
}
}
} }
/** /**