Skip to content

Instantly share code, notes, and snippets.

@leechristensen
Created July 30, 2024 17:39
Show Gist options
  • Save leechristensen/1ae0538963ebd50f63364b4fa00044d1 to your computer and use it in GitHub Desktop.
Save leechristensen/1ae0538963ebd50f63364b4fa00044d1 to your computer and use it in GitHub Desktop.
A script for Ghidra to demangle function names and apply types to functions. Note: Ghidra supports only adding C types to functions. If a C++ type is detected by the demangler, the function will receive a type substituted with "undefined4" instead.. Original source from https://github.com/egorolegovichyakovlev/BorlandCPPDemangler
/*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <http://unlicense.org/>
*/
//A script to demangle Borland C++ and Delphi 4 names.
//Ghidra supports only adding C types to functions. If a C++ type is detected by the demangler, the function will receive a type substituted with "undefined4" instead.
//@category Symbol
import docking.*;
import ghidra.app.context.ProgramSymbolActionContext;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionIterator;
import ghidra.program.model.listing.Variable;
import ghidra.program.model.listing.LocalVariable;
import ghidra.program.model.listing.LocalVariableImpl;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.data.*;
import ghidra.program.util.*;
import ghidra.util.exception.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.app.cmd.function.CreateFunctionCmd;
import ghidra.program.util.string.StringSearcher;
import ghidra.program.util.string.FoundStringCallback;
import ghidra.program.util.string.FoundString;
import ghidra.program.model.address.AddressSetView;
import ghidra.app.services.CodeViewerService;
import java.lang.String;
import java.lang.Character;
import java.util.ArrayList;
import java.nio.ByteBuffer;
import java.awt.*;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class BorlandCPPDemangler extends GhidraScript {
private static final int TABLE_LIMIT = 36;
private static final int LENGTH_LIMIT = 250;
private static boolean EXTRA_PARAMS = false;
private static boolean NOMODIFY = false;
private static boolean ARGSDEMANGLE = false;
private static boolean DEBUG_MSGS = false;
enum SymbolKind {
FUNCTION,
CONSTRUCTOR,
DESTRUCTOR,
OPERATOR,
CONVERSION,
DATA,
THUNK,
TPDSC,
VTABLE,
VRDF_THUNK,
DYN_THUNK
}
enum ModifierUniq {
QUALIFIED,
TEMPLATE,
}
enum Modifier {
VIRDEF_FLAG,
FRIEND_LIST,
CTCH_HNDL_TBL,
OBJ_DEST_TBL,
THROW_LIST,
EXC_CTXT_TBL,
LINKER_PROC,
}
enum VTableFlag {
HUGE,
FASTTHIS,
RTTI
}
private boolean prevTemplClose = false;
private ArrayList<VTableFlag> vtblFlags = new ArrayList<VTableFlag>();
private ArrayList<String> thunkArguments = new ArrayList<String>();
private SymbolKind functionSymbolKind = null;
private Modifier functionModifier = null;
private ArrayList<ModifierUniq> functionUniqModifiers = new ArrayList<ModifierUniq>();
private int charToSkip = -1;
private Function demangleFunc = null;
private char[] funcNameChars;
private String funcName = new String();
private String funcNamePart = new String();
private ArrayList<String> mainArgumentsList = new ArrayList<String>();
private String outputString = new String();
private int charPtr = 0;
private int kindFlag = 0;
private String returnType = new String();
private String callConvent = new String();
private String regConvent = new String();
private String flagsList = new String();
private String onErrorReturn = new String();
private boolean returnSuccess = true;
private JFrame mainFrame;
public class NameError extends Exception {
public NameError(String errorMessage) {
super(errorMessage);
}
}
public class NameEnded extends Exception {
public NameEnded(String errorMessage) {
super(errorMessage);
}
}
private void printDebug(String message) {
if (DEBUG_MSGS == true) {
println("[DEBUG]: " + message);
}
}
@Override
public void run() throws Exception {
mainFrame = new JFrame("Borland C++ Function Demangler");
mainFrame.setMinimumSize(new Dimension(400, 250));
mainFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
JPanel mainPanel = new JPanel();
JButton demangleSingleFunctionButton = new JButton("Demangle single function");
demangleSingleFunctionButton.setAlignmentX(demangleSingleFunctionButton.CENTER_ALIGNMENT);
demangleSingleFunctionButton.setAlignmentY(demangleSingleFunctionButton.CENTER_ALIGNMENT);
demangleSingleFunctionButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
demangleSingle();
}
});
JButton demangleAllFunctionsButton = new JButton("Demangle all functions");
demangleAllFunctionsButton.setAlignmentX(demangleAllFunctionsButton.CENTER_ALIGNMENT);
demangleAllFunctionsButton.setAlignmentY(demangleAllFunctionsButton.CENTER_ALIGNMENT);
demangleAllFunctionsButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
demangleAll();
}
});
JButton findRTTIFunctionsButton = new JButton("Find RTTI function names");
findRTTIFunctionsButton.setAlignmentX(findRTTIFunctionsButton.CENTER_ALIGNMENT);
findRTTIFunctionsButton.setAlignmentY(findRTTIFunctionsButton.CENTER_ALIGNMENT);
findRTTIFunctionsButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
searchForRTTI();
}
});
JCheckBox checkExtParams = new JCheckBox("Print extended parameters");
checkExtParams.setAlignmentX(checkExtParams.CENTER_ALIGNMENT);
checkExtParams.setAlignmentY(checkExtParams.CENTER_ALIGNMENT);
checkExtParams.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JCheckBox source = (JCheckBox)e.getSource();
EXTRA_PARAMS = source.isSelected();
}
});
JCheckBox checkNoModify = new JCheckBox("Don't modify functions (only print names)");
checkNoModify.setAlignmentX(checkNoModify.CENTER_ALIGNMENT);
checkNoModify.setAlignmentY(checkNoModify.CENTER_ALIGNMENT);
checkNoModify.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JCheckBox source = (JCheckBox)e.getSource();
NOMODIFY = source.isSelected();
}
});
JCheckBox checkArgsDemangle = new JCheckBox("Demangle function arguments (may destroy your symbols)");
checkArgsDemangle.setAlignmentX(checkArgsDemangle.CENTER_ALIGNMENT);
checkArgsDemangle.setAlignmentY(checkArgsDemangle.CENTER_ALIGNMENT);
checkArgsDemangle.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JCheckBox source = (JCheckBox)e.getSource();
ARGSDEMANGLE = source.isSelected();
}
});
JCheckBox checkShowVerbose = new JCheckBox("Show verbose logs (lots of output)");
checkShowVerbose.setAlignmentX(checkShowVerbose.CENTER_ALIGNMENT);
checkShowVerbose.setAlignmentY(checkShowVerbose.CENTER_ALIGNMENT);
checkShowVerbose.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JCheckBox source = (JCheckBox)e.getSource();
DEBUG_MSGS = source.isSelected();
}
});
Box innerBox = new Box(BoxLayout.Y_AXIS);
innerBox.add(Box.createVerticalGlue());
innerBox.add(demangleSingleFunctionButton);
innerBox.add(demangleAllFunctionsButton);
innerBox.add(findRTTIFunctionsButton);
innerBox.add(checkExtParams);
innerBox.add(checkNoModify);
innerBox.add(checkArgsDemangle);
innerBox.add(checkShowVerbose);
innerBox.add(Box.createVerticalGlue());
mainFrame.add(innerBox);
mainFrame.pack();
mainFrame.setVisible(true);
}
private void searchForRTTI() {
int RTTITransaction = currentProgram.startTransaction("Search for Borland RTTI function names");
byte[] magicNum = new byte[] {0x3, 0x0, 0x30, 0x0};
Address foundMagic1 = currentProgram.getMemory().findBytes(currentProgram.getMinAddress(), magicNum, null, true, monitor);
if (foundMagic1 == null) {
printerr("No RTTI symbols found");
}
while (foundMagic1 != null) {
Address checkMagicAddress = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(foundMagic1.getOffset() + 20);
Address checkMagicAddress2 = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(foundMagic1.getOffset() + 24);
Address foundMagicString = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(foundMagic1.getOffset() + 44);
Address foundMagicStringEnd = currentProgram.getMemory().findBytes(foundMagicString, new byte[] {0x0}, null, true, monitor);
try {
int foundIsMagic = currentProgram.getMemory().getInt(checkMagicAddress);
int foundIsMagic2 = currentProgram.getMemory().getInt(checkMagicAddress2);
if (foundIsMagic == 0x0 && foundIsMagic2 == 0x0) {
StringSearcher searchForStrings = new StringSearcher(currentProgram, 1, 1, true, true);
AddressSetView searcherAddress = searchForStrings.search(new AddressSet(foundMagicString, foundMagicStringEnd), callbackString, true, monitor);
}
} catch (MemoryAccessException exc) {}
if (foundMagicStringEnd == null) {
foundMagic1 = currentProgram.getMemory().findBytes(foundMagicString, magicNum, null, true, monitor);
} else {
foundMagic1 = currentProgram.getMemory().findBytes(foundMagicStringEnd, magicNum, null, true, monitor);
}
}
currentProgram.endTransaction(RTTITransaction, true);
}
private FoundStringCallback callbackString = new FoundStringCallback() {
@Override
public void stringFound(FoundString foundString) {
String gotString = foundString.getString(currentProgram.getMemory());
Address gotAddress = foundString.getAddress();
try {
Address funcPointerAddress = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(gotAddress.getOffset() - 8);
Address funcAddress = currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(currentProgram.getMemory().getInt(funcPointerAddress));
Function foundFunc = currentProgram.getFunctionManager().getFunctionAt(funcAddress);
if (NOMODIFY == false) {
String filteredName = gotString.replace(" ", "");
if (foundFunc == null) {
CreateFunctionCmd funcmd = new CreateFunctionCmd(funcAddress);
funcmd.applyTo(currentProgram, monitor);
foundFunc = currentProgram.getFunctionManager().getFunctionAt(funcAddress);
}
if (foundFunc != null) {
if (foundFunc.getName().contains(filteredName) == false) {
try {
foundFunc.setName(filteredName, SourceType.IMPORTED);
} catch (DuplicateNameException exc) {
} catch (InvalidInputException exc) {
printerr("Function name could not be set (illegal characters): " + filteredName);
}
}
}
}
println("Found function \"" + gotString + "\" at 0x" + funcAddress.toString());
} catch (MemoryAccessException exc) {}
}
};
private void demangleAll() {
int demangleTransaction = currentProgram.startTransaction("Demangle multiple Borland functions");
FunctionIterator funList = currentProgram.getListing().getFunctions(true);
while (funList.hasNext()) {
Function toDemangle = funList.next();
String funName = toDemangle.getName();
if (funName.startsWith("@")) {
println("\"" + funName + "\" -> \"" + demangleBorlandFunction(toDemangle) + "\"");
}
}
currentProgram.endTransaction(demangleTransaction, true);
}
private void demangleSingle() {
int demangleTransaction = currentProgram.startTransaction("Demangle single Borland function");
CodeViewerService codeViewer = state.getTool().getService(CodeViewerService.class);
ProgramLocation location = codeViewer.getCurrentLocation();
Address address = location.getAddress();
Function funAddr = getFunctionAt(address);
println("\"" + funAddr.getName() + "\" -> \"" + demangleBorlandFunction(funAddr) + "\"");
currentProgram.endTransaction(demangleTransaction, true);
}
private void flushDemangler() {
functionSymbolKind = null;
functionModifier = null;
functionUniqModifiers = new ArrayList<ModifierUniq>();
charToSkip = -1;
funcNameChars = null;
funcName = new String();
funcNamePart = new String();
mainArgumentsList = new ArrayList<String>();
outputString = new String();
charPtr = 0;
kindFlag = 0;
thunkArguments = new ArrayList<String>();
vtblFlags = new ArrayList<VTableFlag>();
returnType = new String();
callConvent = new String();
regConvent = new String();
flagsList = new String();
onErrorReturn = new String();
returnSuccess = true;
}
private String demangleBorlandFunction(Function borlandFun) {
demangleFunc = borlandFun;
if (borlandFun == null) {
printerr("No function selected!");
return "";
}
String returnDemangled = demangleBorlandString(borlandFun.getName());
if (returnDemangled == null) {
return "";
}
if (NOMODIFY == false) {
String filteredFuncName = funcNamePart.replace("(", "").replace(")", "").replace("[", "").replace("]", "");
if (filteredFuncName.startsWith("operator ")) {
filteredFuncName = filteredFuncName.replaceFirst("operator ", "");
}
if (filteredFuncName.contains(" operator ")) {
filteredFuncName = filteredFuncName.replace(" operator ", "");
}
if (filteredFuncName.startsWith("__closure ")) {
filteredFuncName = filteredFuncName.replaceFirst("__closure ", "");
}
if (filteredFuncName.contains(" __closure ")) {
filteredFuncName = filteredFuncName.replace(" __closure ", "");
}
filteredFuncName = filteredFuncName.replace(" ", "");
try {
borlandFun.setName(filteredFuncName, SourceType.IMPORTED);
} catch (DuplicateNameException exc) {
} catch (InvalidInputException exc) {
printerr("Function name could not be set (illegal characters): " + filteredFuncName);
}
}
String funConvent = borlandFun.getCallingConventionName();
if (callConvent == "__cdecl" || callConvent == "__fastcall" || callConvent == "__stdcall") {
funConvent = callConvent;
}
DataType retTypeData = findDataType(returnType);
printDebug("Calling convention detected: " + funConvent);
printDebug("Return type detected: " + retTypeData.getName() + " -- " + returnType);
Variable retTypeVariable = borlandFun.getReturn();
try {
DataType undefData = new Undefined4DataType();
if (retTypeData.isEquivalent(undefData) == false) {
retTypeVariable.setDataType(retTypeData, SourceType.IMPORTED);
}
} catch (InvalidInputException exc) {
}
int stackOffsetVal = retTypeData.getLength();
int funCounter = 0;
ArrayList<Variable> localParamsNew = new ArrayList<Variable>();
while (funCounter < mainArgumentsList.size()) {
DataType argTypeData = findDataType(mainArgumentsList.get(funCounter));
Variable argVar = null;
try {
argVar = new LocalVariableImpl("param_" + (funCounter+1), argTypeData, stackOffsetVal, currentProgram);
localParamsNew.add(argVar);
printDebug("Argument " + funCounter + " \"" + argTypeData.getName() + "\" - \"" + mainArgumentsList.get(funCounter) + "\"");
stackOffsetVal += argTypeData.getLength();
} catch (InvalidInputException exc) {
}
funCounter += 1;
}
if (returnSuccess == true && NOMODIFY == false && ARGSDEMANGLE == true) {
try {
borlandFun.updateFunction(
funConvent,
retTypeVariable,
localParamsNew,
Function.FunctionUpdateType.CUSTOM_STORAGE,
false,
SourceType.IMPORTED
);
} catch (InvalidInputException exc) {
} catch (DuplicateNameException exc) {
}
}
String extraParams = new String();
boolean isQualified = false;
boolean isTemplate = false;
int modifiersLen = 0;
while (modifiersLen < functionUniqModifiers.size()) {
switch (functionUniqModifiers.get(modifiersLen)) {
case QUALIFIED:
isQualified = true;
break;
case TEMPLATE:
isTemplate = true;
break;
}
modifiersLen += 1;
}
if (isQualified == true) {
extraParams += "qualified ";
}
if (isTemplate == true) {
extraParams += "template ";
}
if (functionModifier != null) {
switch (functionModifier) {
case VIRDEF_FLAG:
extraParams += "virdef_flag ";
break;
case FRIEND_LIST:
extraParams += "friend_list ";
break;
case CTCH_HNDL_TBL:
extraParams += "catch_handler_table ";
break;
case OBJ_DEST_TBL:
extraParams += "object_destructor_table ";
break;
case THROW_LIST:
extraParams += "throw_list ";
break;
case EXC_CTXT_TBL:
extraParams += "exception_context_table ";
break;
}
}
if (functionSymbolKind != null) {
switch (functionSymbolKind) {
case FUNCTION:
extraParams += "function";
break;
case CONSTRUCTOR:
extraParams += "constructor";
break;
case DESTRUCTOR:
extraParams += "destructor";
break;
case OPERATOR:
extraParams += "operator";
break;
case CONVERSION:
extraParams += "conv_operator";
break;
case DATA:
extraParams += "data";
break;
case THUNK:
extraParams += "thunk";
break;
case TPDSC:
extraParams += "typedesc";
break;
case VTABLE:
extraParams += "vtable";
break;
case VRDF_THUNK:
extraParams += "virdef_thunk";
break;
}
}
demangleFunc = null;
if (EXTRA_PARAMS == true) {
returnDemangled = extraParams + " " + returnDemangled;
}
return returnDemangled;
}
private String demangleBorlandString(String name) {
flushDemangler();
boolean errorDetected = false;
String returnString = new String();
if (name.charAt(0) != '@') {
printerr("Function is not mangled!");
return null;
} else {
funcName = name.substring(1, name.length());
}
String[] allSplits = name.split("@");
char[] allPart = name.toCharArray();
char[] allPartReverse = name.toCharArray();
char[] lastPart = allSplits[allSplits.length-1].toCharArray();
boolean notMsPart = false;
if (lastPart.length > 1) {
for (int chPart = 0; chPart < lastPart.length; chPart += 1) {
if (Character.isDigit(lastPart[chPart]) == false) {
notMsPart = true;
}
}
} else {
notMsPart = true;
}
if (notMsPart == false) {
printerr("Function is Microsoft mangled!");
return null;
}
boolean isPascal = true;
for (int i = allPart.length-1; i >= 0; i -= 1) {
if (allPart[i] >= 'a' && allPart[i] <= 'z') {
isPascal = false;
break;
}
}
if (isPascal == true) {
funcName = funcName.toLowerCase();
} else {
printDebug("Not pascal");
}
funcNameChars = funcName.toCharArray();
try {
returnString += copyName(false, true, -1);
if (functionSymbolKind == SymbolKind.TPDSC || functionModifier != null) {
/* Previous name */
String[] spaceSplit = returnString.split(" ");
returnString = spaceSplit[spaceSplit.length-1];
}
if (functionSymbolKind == SymbolKind.CONSTRUCTOR || functionSymbolKind == SymbolKind.DESTRUCTOR) {
String[] qualsList = returnString.split("::");
String qualLast = qualsList[qualsList.length-1];
if (functionSymbolKind == SymbolKind.DESTRUCTOR) {
returnString += "~";
}
returnString += qualLast;
}
printDebug("After name copied: " + returnString);
funcNamePart = returnString;
boolean doArgs = true;
/* Copy arguments */
if (thisChar() == '$' && doArgs == true) {
char charHoldNext;
charHoldNext = nextChar();
if (charHoldNext != 'q' && charHoldNext != 'x' && charHoldNext != 'w') {
printerr("Symbol parsing failed: wrong argument character sequence at " + charPtr);
return null;
}
returnString += copyType(false, true);
if (functionSymbolKind == null) {
functionSymbolKind = SymbolKind.FUNCTION;
}
} else if (functionSymbolKind == null) {
functionSymbolKind = SymbolKind.DATA;
} else if (vtblFlags.isEmpty() == false) {
String vtblString = new String();
for (int i = 0; i < vtblFlags.size(); i += 1) {
if (vtblString.length() > 0) {
vtblString += ", ";
}
switch (vtblFlags.get(i)) {
case HUGE:
vtblString += "huge";
break;
case FASTTHIS:
vtblString += "fastthis";
break;
case RTTI:
vtblString += "rtti";
break;
}
}
returnString += " (" + vtblString + ")";
}
} catch (NameError err) {
printerr("Name error: " + err.getMessage());
printerr("Name which errored: @" + funcName);
errorDetected = true;
returnString += onErrorReturn+"...";
printerr(returnString);
returnSuccess = false;
}
if (errorDetected == false) {
if (returnType.isEmpty() == false) {
returnString = returnType + " " + returnString;
}
if (callConvent.isEmpty() == false) {
returnString = callConvent + " " + returnString;
}
if (regConvent.isEmpty() == false) {
returnString = regConvent + " " + returnString;
}
if (flagsList.isEmpty() == false) {
returnString = flagsList + returnString;
}
}
return returnString;
}
private DataType findDataType(String typeToFind) {
DataType typeToReturn = new Undefined4DataType();
String argText = typeToFind;
String mainArg = argText;
if (mainArg.startsWith("operator ")) {
mainArg = mainArg.replaceFirst("operator ", "");
}
if (mainArg.contains(" operator ")) {
mainArg = mainArg.replace(" operator ", "");
}
if (mainArg.startsWith("__closure ")) {
mainArg = mainArg.replaceFirst("__closure ", "");
}
if (mainArg.contains(" __closure ")) {
mainArg = mainArg.replace(" __closure ", "");
}
if (mainArg.startsWith("const ")) {
mainArg = mainArg.replaceFirst("const ", "");
}
if (mainArg.startsWith("volatile ")) {
mainArg = mainArg.replaceFirst("volatile ", "");
}
String argType = new String();
boolean isUnsigned = false;
if (mainArg.startsWith("unsigned ")) {
mainArg = mainArg.replaceFirst("unsigned ", "");
isUnsigned = true;
} else if (mainArg.startsWith("signed ")) {
mainArg = mainArg.replaceFirst("signed ", "");
}
if (mainArg.startsWith("char")) {
mainArg = mainArg.replaceFirst("char", "");
argType = "char";
typeToReturn = new CharDataType();
if (isUnsigned == true) {
argType = "u" + argType;
typeToReturn = new UnsignedCharDataType();
}
} else if (mainArg.startsWith("void")) {
mainArg = mainArg.replaceFirst("void", "");
typeToReturn = new VoidDataType();
argType = "void";
} else if (mainArg.startsWith("wchar_t")) {
mainArg = mainArg.replaceFirst("wchar_t", "");
typeToReturn = new WideCharDataType();
argType = "wchar_t";
} else if (mainArg.startsWith("short")) {
mainArg = mainArg.replaceFirst("short", "");
argType = "short";
typeToReturn = new ShortDataType();
if (isUnsigned == true) {
argType = "u" + argType;
typeToReturn = new UnsignedShortDataType();
}
} else if (mainArg.startsWith("int")) {
mainArg = mainArg.replaceFirst("int", "");
argType = "int";
typeToReturn = new IntegerDataType();
if (isUnsigned == true) {
argType = "u" + argType;
typeToReturn = new UnsignedIntegerDataType();
}
} else if (mainArg.startsWith("long long")) {
mainArg = mainArg.replaceFirst("long long", "");
argType = "longlong";
typeToReturn = new LongLongDataType();
if (isUnsigned == true) {
argType = "u" + argType;
typeToReturn = new UnsignedLongLongDataType();
}
} else if (mainArg.startsWith("long")) {
mainArg = mainArg.replaceFirst("long", "");
argType = "long";
typeToReturn = new LongDataType();
if (isUnsigned == true) {
argType = "u" + argType;
typeToReturn = new UnsignedLongDataType();
}
} else if (mainArg.startsWith("float")) {
mainArg = mainArg.replaceFirst("float", "");
argType = "float";
typeToReturn = new FloatDataType();
} else if (mainArg.startsWith("double")) {
mainArg = mainArg.replaceFirst("double", "");
argType = "double";
typeToReturn = new DoubleDataType();
} else if (mainArg.startsWith("long double")) {
mainArg = mainArg.replaceFirst("long double", "");
argType = "longdouble";
isUnsigned = false;
typeToReturn = new LongDoubleDataType();
} else if (mainArg.startsWith("__int64")) {
mainArg = mainArg.replaceFirst("__int64", "");
argType = "INT64";
typeToReturn = new LongLongDataType();
if (isUnsigned == true) {
argType = "U" + argType;
typeToReturn = new UnsignedLongLongDataType();
}
} else if (mainArg.startsWith("bool")) {
mainArg = mainArg.replaceFirst("bool", "");
argType = "bool";
} else {
argType = "undefined4";
isUnsigned = false;
}
String innerPointers = new String();
if ((mainArg.equals(" *") || mainArg.equals(" * *")) && argType != "longdouble") {
if (mainArg.equals(" * *")) {
typeToReturn = new PointerDataType(new PointerDataType(typeToReturn));
mainArg = "";
} else {
typeToReturn = new PointerDataType(typeToReturn);
}
innerPointers = mainArg;
printDebug("Inner pointers: " + innerPointers);
} else if (mainArg.equals("&")) {
innerPointers = " *";
typeToReturn = new PointerDataType(typeToReturn);
}
printDebug("Inner arg: " + mainArg);
argType += innerPointers;
return typeToReturn;
}
private String copyLastOp(String opCopy) {
switch (opCopy) {
case "add":
return "+";
case "adr":
return "&";
case "and":
return "&";
case "asg":
return "=";
case "land":
return "&&";
case "lor":
return "||";
case "call":
return "()";
case "cmp":
return "~";
case "fnc":
return "()";
case "dec":
return "--";
case "dele":
return "delete";
case "div":
return "/";
case "eql":
return "==";
case "geq":
return ">=";
case "gtr":
return ">";
case "inc":
return "++";
case "ind":
return "*";
case "leq":
return "<=";
case "lsh":
return "<<";
case "lss":
return "<";
case "mod":
return "%";
case "mul":
return "*";
case "neq":
return "!=";
case "new":
return "new";
case "not":
return "!";
case "or":
return "|";
case "rand":
return "&=";
case "rdiv":
return "/=";
case "rlsh":
return "<<=";
case "rmin":
return "-=";
case "rmod":
return "%=";
case "rmul":
return "*=";
case "ror":
return "|=";
case "rplu":
return "+=";
case "rrsh":
return ">>=";
case "rsh":
return ">>";
case "rxor":
return "^=";
case "subs":
return "[]";
case "sub":
return "-";
case "xor":
return "^";
case "arow":
return "->";
case "nwa":
return "new[]";
case "dla":
return "delete[]";
}
return "";
}
private String copyUntil(char finishingCharacter) throws NameError {
String returnString = new String();
char charHold = thisChar();
while (charHold != finishingCharacter && charPtr < funcName.length() && charHold != 0) {
returnString += charHold;
charHold = nextChar();
}
return returnString;
}
private String copyUntil2(char finishingCharacter, char finishingCharacter2) throws NameError {
String returnString = new String();
char charHold = thisChar();
while (charHold != finishingCharacter && charHold != finishingCharacter2 && charPtr < funcName.length() && charHold != 0) {
returnString += charHold;
charHold = nextChar();
}
return returnString;
}
private boolean nameEnded() {
if (charPtr >= funcName.length()-1) {
return true;
}
return false;
}
private String copyArgsDelphi(char finishingCharacter, boolean templArgs) throws NameError {
String returnString = new String();
try {
char charHold = thisChar();
boolean first = true;
char termChar = 0;
while (charHold != finishingCharacter) {
int charBegin = charPtr;
if (first == true) {
first = false;
} else {
returnString += ", ";
}
/* Skip the kind character */
nextChar();
switch (charHold) {
case 't':
returnString += copyType(!templArgs, false);
break;
case 'T':
returnString += "<type ";
termChar = '>';
/* Fallthrough */
case 'i':
if (funcName.charAt(charBegin) == '4' && funcName.substring(charBegin+1, charBegin+5) == "bool") {
if (thisChar() == '0') {
returnString += "false";
} else {
returnString += "true";
}
nextChar();
break;
}
case 'j':
case 'g':
case 'e':
/* Skip type */
copyType(!templArgs, false);
if (thisChar() != '$') {
throw new NameError("Symbol parsing failed: arglist expected at " + charPtr);
}
nextChar();
returnString += copyUntil2('$', '%');
if (termChar != 0) {
returnString += termChar;
}
break;
case 'm':
/* Skip type */
copyType(!templArgs, false);
if (thisChar() != '$') {
throw new NameError("Symbol parsing failed: arglist expected at " + charPtr);
}
nextChar();
returnString += copyUntil('$');
returnString += "::*";
returnString += copyUntil2('$', '%');
break;
default:
throw new NameError("Unknown template arg kind at " + charPtr);
}
charHold = thisChar();
if (charHold != finishingCharacter) {
if (charHold != '$') {
throw new NameError("Symbol parsing failed: arglist expected at " + charPtr);
}
charHold = nextChar();
}
}
} catch (NameError err) {
onErrorReturn = returnString + onErrorReturn;
throw err;
}
return returnString;
}
private int clauseCounter = 1;
private String copyName(boolean templName, boolean isMaster, int untilChar) throws NameError {
printDebug("Copy name called");
String returnString = new String();
String returnFlags = new String();
boolean isDelphi = false;
try {
char charHold = thisChar();
while (true) {
if (Character.isDigit(charHold)) {
printDebug("Copy flags digit");
int flags = charHold - '0' + 1;
if ((flags & 1) > 0) {
vtblFlags.add(VTableFlag.HUGE);
}
if ((flags & 2) > 0) {
vtblFlags.add(VTableFlag.FASTTHIS);
}
if ((flags & 4) > 0) {
vtblFlags.add(VTableFlag.RTTI);
}
functionSymbolKind = SymbolKind.VTABLE;
charHold = nextChar();
if (charHold != 0 && charHold != '$') {
throw new NameError("Symbol parsing failed: arglist expected at " + charPtr);
}
}
printDebug("Switch was called");
switch (charHold) {
/* Qualifier */
case '@':
charHold = nextChar();
if (charHold == '$') {
if (nextChar() != 'c') {
throw new NameError("Symbol parsing failed: wrong qualifier character sequence at " + charPtr);
}
if (nextChar() != 'f') {
throw new NameError("Symbol parsing failed: wrong qualifier character sequence at " + charPtr);
}
if (nextChar() != '$') {
throw new NameError("Symbol parsing failed: wrong qualifier character sequence at " + charPtr);
}
if (nextChar() != '@') {
throw new NameError("Symbol parsing failed: wrong qualifier character sequence at " + charPtr);
}
returnFlags = "__vdflg__ ";
if (isMaster == true) {
flagsList += returnFlags;
} else {
returnString += returnFlags;
}
nextChar();
String retStrName = copyName(false, false, -1);
returnString += retStrName;
functionModifier = Modifier.VIRDEF_FLAG;
} else {
returnFlags = "__linkproc__ ";
if (isMaster == true) {
flagsList += returnFlags;
} else {
returnString += returnFlags;
}
String retStrName = copyName(false, false, -1);
returnString += retStrName;
functionModifier = Modifier.LINKER_PROC;
}
printDebug("Qualifier found returned");
return returnString;
/* Tmplcode */
case '%':
printDebug("Tmplcode detected at " + charPtr);
charHold = nextChar();
if (charHold == 'S' || charHold == 'D') {
String restSubStr = funcName.substring(charPtr, funcName.length());
if (restSubStr.startsWith("Set$") || restSubStr.startsWith("DynamicArray$") || restSubStr.startsWith("SmallString$") || restSubStr.startsWith("DelphiInterface$")) {
isDelphi = true;
printDebug("Delphi argument detected!");
}
}
String retStrName = copyName(true, false, -1);
returnString += retStrName;
/* Arglist */
if (thisChar() != '$') {
throw new NameError("Symbol parsing failed: arglist expected at " + thisChar() + " " + charPtr);
}
nextChar();
if (returnString.charAt(returnString.length()-1) == '<') {
returnString += " ";
}
returnString += "<";
int counterCl = clauseCounter;
clauseCounter+= 1;
String argsStr = new String();
if (isDelphi == true) {
argsStr = copyArgsDelphi('%', true);
} else {
argsStr = copyArgs('%', true, false);
}
returnString += argsStr;
if (returnString.charAt(returnString.length()-1) == '>') {
returnString += " ";
}
returnString += ">";
if (thisChar() != '%') {
throw new NameError("Symbol parsing failed: tmplcode expected at " + charPtr);
}
nextChar();
if (thisChar() != '@') {
if (functionUniqModifiers.contains(ModifierUniq.TEMPLATE) == false) {
functionUniqModifiers.add(ModifierUniq.TEMPLATE);
}
}
break;
/* Arglist */
case '$':
printDebug("Arglist detected");
if (templName == true) {
return returnString;
}
charHold = nextChar();
if (charHold == 'x') {
charHold = nextChar();
if (charHold == 'p' || charHold == 't') {
if (nextChar() != '$') {
throw new NameError("Symbol parsing failed: wrong arglist character sequence at " + charPtr);
}
nextChar();
returnFlags = "__tpdsc__ ";
if (isMaster == true) {
flagsList += returnFlags;
} else {
returnString += returnFlags;
}
returnString += copyType(false, false);
functionSymbolKind = SymbolKind.TPDSC;
return returnString;
} else {
throw new NameError("Symbol parsing failed: wrong arglist character sequence at " + charPtr);
}
}
if (charHold == 'b') {
charHold = nextChar();
int charPtrHold = charPtr;
if (charHold == 'c' || charHold == 'd') {
if (nextChar() == 't') {
if (nextChar() == 'r') {
if (nextChar() != '$') {
throw new NameError("Symbol parsing failed: arglist expected at " + charPtr);
}
if (charHold == 'c') {
functionSymbolKind = SymbolKind.CONSTRUCTOR;
} else {
functionSymbolKind = SymbolKind.DESTRUCTOR;
}
break;
}
}
}
charPtr = charPtrHold;
returnString += "operator ";
returnString += copyLastOp(copyUntil('$'));
functionSymbolKind = SymbolKind.OPERATOR;
} else if (charHold == 'o') {
nextChar();
returnString += "operator ";
returnString += copyType(false, false);
if (thisChar() != '$') {
throw new NameError("Symbol parsing failed: arglist expected at " + charPtr);
}
functionSymbolKind = SymbolKind.CONVERSION;
} else if (charHold == 'v' || charHold == 'd') {
char tKind = charHold;
charHold = nextChar();
if (tKind == 'v' && charHold == 's') {
charHold = nextChar();
if (charHold != 'f' && charHold != 'n') {
throw new NameError("Symbol parsing failed: wrong arglist character sequence at " + charPtr);
}
nextChar();
returnFlags = "__vdthk__";
if (isMaster == true) {
flagsList += returnFlags;
} else {
returnString += returnFlags;
}
functionSymbolKind = SymbolKind.VRDF_THUNK;
} else if (charHold == 'c') {
charHold = nextChar();
if (Character.isDigit(charHold) == false) {
throw new NameError("Symbol parsing failed: wrong arglist character sequence at " + charPtr);
}
charHold = nextChar();
if (charHold != '$') {
throw new NameError("Symbol parsing failed: wrong arglist character sequence at " + charPtr);
}
charHold = nextChar();
returnFlags = "__thunk__ [";
if (tKind == 'v') {
functionSymbolKind = SymbolKind.THUNK;
} else {
functionSymbolKind = SymbolKind.DYN_THUNK;
}
thunkArguments.add(""+charHold);
returnFlags += charHold + ",";
charHold = nextChar();
String newFlagArg1 = copyUntil('$');
thunkArguments.add(""+newFlagArg1);
returnFlags += newFlagArg1;
returnFlags += charHold + ",";
charHold = nextChar();
String newFlagArg2 = copyUntil('$');
thunkArguments.add(""+newFlagArg2);
returnFlags += newFlagArg2;
returnFlags += charHold + ",";
charHold = nextChar();
String newFlagArg3 = copyUntil('$');
thunkArguments.add(""+newFlagArg3);
returnFlags += newFlagArg3;
returnFlags += "]";
if (isMaster == true) {
flagsList += returnFlags;
} else {
returnString += returnFlags;
}
/* Skip last arglist */
nextChar();
return returnString;
}
} else {
throw new NameError("Symbol parsing failed: unknown special name at " + charPtr);
}
break;
case '_':
int charPtrCopied = charPtr;
if (nextChar() == '$') {
charHold = nextChar();
/*
In Borland there are five kinds of special names:
frndl | FL | friend list
chtbl | CH | catch handler table
odtbl | DC | object destructor table
thrwl | TL | throw list
ectbl | ECT | exception context table
*/
returnFlags = "__";
switch ((funcName.charAt(charPtr)<<8)|funcName.charAt(charPtr+1)) {
/* Friend list */
case 0x464c:
returnFlags += "frndl";
functionModifier = Modifier.FRIEND_LIST;
break;
/* Catcher handle table */
case 0x4348:
returnFlags += "chtbl";
functionModifier = Modifier.CTCH_HNDL_TBL;
break;
/* Object destructor table */
case 0x4443:
returnFlags += "odtbl";
functionModifier = Modifier.OBJ_DEST_TBL;
break;
/* Throw list */
case 0x544c:
returnFlags += "thrwl";
functionModifier = Modifier.THROW_LIST;
break;
/* Exception context table */
case 0x4543:
returnFlags += "ectbl";
functionModifier = Modifier.EXC_CTXT_TBL;
break;
}
returnFlags += "__ ";
if (isMaster == true) {
flagsList += returnFlags;
} else {
returnString += returnFlags;
}
while (charHold >= 'A' && charHold <= 'Z') {
charHold = nextChar();
}
if (charHold != '$') {
throw new NameError("Symbol parsing failed: arglist expected at " + charPtr);
}
if (nextChar() != '@') {
throw new NameError("Symbol parsing failed: qualifier expected at " + charPtr);
}
nextChar();
returnString += copyName(false, false, -1);
return returnString;
}
charPtr = charPtrCopied;
/* Fallthrough */
default:
printDebug("Run copy until");
returnString += copyUntil2('@', '$');
break;
}
charHold = thisChar();
if (charHold != 0 && charHold != '@' && charHold != '$') {
throw new NameError("Symbol parsing failed: found wrong character sequence at " + charPtr);
}
if (charHold == '@') {
charHold = nextChar();
if (functionUniqModifiers.contains(ModifierUniq.QUALIFIED) == false) {
functionUniqModifiers.add(ModifierUniq.QUALIFIED);
}
returnString += "::";
if (charHold != 0) {
functionSymbolKind = SymbolKind.VTABLE;
}
} else {
break;
}
}
printDebug("End copy name");
} catch (NameError err) {
onErrorReturn = returnString + onErrorReturn;
throw err;
}
return returnString;
}
private char nextChar() {
charPtr += 1;
if (charPtr > funcName.length()-1) {
return 0;
}
printDebug("New char is: " + charPtr + " " + funcNameChars[charPtr]);
return funcNameChars[charPtr];
}
private char thisChar() {
if (charPtr > funcName.length()-1) {
return 0;
}
return funcNameChars[charPtr];
}
private String copyType(boolean argLevel, boolean mainType) throws NameError {
printDebug("Run copy type");
String returnString = new String();
try {
String tName = new String();
char charHold = thisChar();
boolean typeUnsigned = false;
boolean typeSigned = false;
boolean typeConst = false;
boolean typeVolatile = false;
int loopLimit = 100;
char saveChar = 0;
boolean loopStop = false;
/* Process type qualifiers */
while (loopStop == false) {
loopLimit -= 1;
if (loopLimit == 0) {
throw new NameError("Symbol parsing failed: type copying loop out of range at " + charPtr);
}
switch (charHold) {
case 'u':
typeUnsigned = true;
break;
case 'z':
typeSigned = true;
break;
case 'x':
typeConst = true;
break;
case 'w':
typeVolatile = true;
break;
case 'y':
charHold = nextChar();
if (charHold != 'f' && charHold != 'n') {
throw new NameError("Symbol parsing failed: type closure syntax invalid at " + charPtr);
}
returnString += "__closure";
break;
default:
loopStop = true;
break;
}
if (loopStop == false) {
charHold = nextChar();
}
}
/* Enum or class name */
if (Character.isDigit(charHold) == true) {
int i = 0;
/* Determine length */
String lenString = new String();
do {
lenString += charHold;
charHold = nextChar();
} while (Character.isDigit(charHold) == true);
i = Integer.parseInt(lenString);
int m = charPtr;
for (int len = i; len > 0; m += 1) {
if (m > LENGTH_LIMIT || m > funcName.length()) {
throw new NameError("Unexpected name's end at " + m);
}
len -= 1;
}
if (typeConst == true) {
returnString += "const ";
}
if (typeVolatile == true) {
returnString += "volatile ";
}
char toSave = 0;
int charToSkip = charPtr+i;
if (charToSkip < funcName.length()) {
toSave = funcNameChars[charToSkip];
funcNameChars[charToSkip] = 0;
}
printDebug("New function name: " + funcName);
String retStrName = copyName(false, false, charPtr+i);
returnString += retStrName;
if (charToSkip < funcName.length()) {
printDebug("Char zeroed: " + charToSkip + " Char current: " + charPtr);
funcNameChars[charPtr] = toSave;
}
printDebug("Return argument");
return returnString;
}
saveChar = charHold;
switch (charHold) {
case 'v':
tName = "void";
break;
case 'c':
tName = "char";
break;
case 'b':
tName = "wchar_t";
break;
case 's':
tName = "short";
break;
case 'i':
tName = "int";
break;
case 'l':
tName = "long";
break;
case 'f':
tName = "float";
break;
case 'd':
tName = "double";
break;
case 'g':
tName = "long double";
break;
case 'j':
tName = "__int64";
break;
case 'o':
tName = "bool";
break;
case 'e':
tName = "...";
break;
/* Member pointer */
case 'M':
nextChar();
outputString += copyType(false, false);
/* Reference */
case 'r':
/* Pointer */
case 'p':
charHold = nextChar();
/* Function pointer */
if (charHold == 'q') {
returnString += "(";
if (saveChar == 'M') {
if (outputString.isEmpty() == true) {
throw new NameError("Type pointer output is empty at " + charPtr);
}
returnString += outputString + "::";
}
returnString += "*)";
saveChar = charHold;
}
printDebug("Pointer type copy");
returnString += copyType(false, false);
if (saveChar == 'r') {
returnString += "&";
} else if (saveChar == 'p') {
returnString += " *";
} else if (saveChar == 'M') {
if (outputString.isEmpty()) {
throw new NameError("Symbol parsing failed: wrong pointer character sequence at " + charPtr);
}
returnString += " " + outputString + "::*";
}
break;
/* Array */
case 'a':
String dimStr = new String();
do {
charHold = nextChar();
dimStr += "[";
if (charHold == '0') {
charHold = nextChar();
}
dimStr += copyUntil('$');
if (charHold != '$') {
throw new NameError("Symbol parsing failed: arglist expected at " + charPtr);
}
charHold = nextChar();
dimStr += "]";
} while (charHold == 'a');
returnString += copyType(false, false);
returnString += dimStr;
break;
/* Function */
case 'q':
printDebug("Function intro detected");
boolean hasReturn = false;
boolean saveAdjQual = false;
while (true) {
if (nextChar() != 'q') {
break;
}
switch (nextChar()) {
case 'c':
callConvent = "__cdecl";
break;
case 'p':
callConvent = "__pascal";
break;
case 'r':
callConvent = "__fastcall";
break;
case 'f':
callConvent = "__fortran";
break;
case 's':
callConvent = "__stdcall";
break;
case 'y':
callConvent = "__syscall";
break;
case 'i':
callConvent = "__interrupt";
break;
case 'g':
regConvent = "__saveregs";
break;
}
}
returnString += "(";
printDebug("Run copy args");
if (mainType == true) {
returnString += copyArgs('$', false, true);
} else {
returnString += copyArgs('$', false, false);
}
returnString += ")";
if (thisChar() == '$') {
hasReturn = true;
nextChar();
}
if (hasReturn == true) {
returnType = copyType(false, false);
printDebug("Return type new is: " + returnType);
}
break;
default:
throw new NameError("Unknown type at " + charPtr);
}
if (tName.isEmpty() == false) {
if (typeConst == true) {
returnString += "const ";
}
if (typeVolatile == true) {
returnString += "volatile ";
}
if (typeSigned == true) {
returnString += "signed ";
}
if (typeUnsigned == true) {
returnString += "unsigned ";
}
if (argLevel == false || saveChar != 'v') {
returnString += tName;
}
nextChar();
} else {
if (typeConst == true) {
returnString += " const";
}
if (typeVolatile == true) {
returnString += " volatile";
}
}
printDebug("Finish copy type");
} catch (NameError err) {
onErrorReturn = returnString + onErrorReturn;
throw err;
}
return returnString;
}
private String copyArgs(char finishingCharacter, boolean templArgs, boolean mainArgs) throws NameError {
String[] paramStrings = new String[TABLE_LIMIT];
String returnString = new String();
String argString = new String();
printDebug("Copyargs initialized");
try {
char charHold = thisChar();
boolean atStart = true;
int paramIndex = 0;
while (charHold != 0 && charHold != finishingCharacter) {
argString = new String();
printDebug("Copying arguments, from char: " + charPtr);
if (atStart == true) {
atStart = false;
} else {
returnString += ", ";
}
int savePtr = charPtr;
boolean delphiScanned = false;
while (charHold == 'x' || charHold == 'w') {
delphiScanned = true;
charHold = nextChar();
}
if (delphiScanned == true && charHold != 't') {
printDebug("Delphi scanned");
charPtr = savePtr;
}
if (charHold != 't') {
printDebug("Call copyType argument");
paramStrings[paramIndex] = copyType(!templArgs, false);
argString += paramStrings[paramIndex];
} else {
int indxVal;
charHold = nextChar();
if (Character.isDigit(charHold)) {
indxVal = charHold - '0';
} else {
indxVal = (charHold - 'a') + 10;
}
indxVal -= 1;
if (paramStrings[indxVal] == null) {
throw new NameError("Symbol parsing failed: name not found at index " + indxVal);
}
argString += paramStrings[indxVal];
nextChar();
}
paramIndex += 1;
charHold = thisChar();
if (templArgs == true && charHold == '$') {
char termChar = 0;
charHold = nextChar();
nextChar();
switch (charHold) {
case 'T':
argString += "<type ";
termChar = '>';
/* Fallthrough */
case 'i':
if (funcName.charAt(savePtr) == '4' && funcName.substring(savePtr+1, savePtr+5) == "bool") {
if (thisChar() == '0') {
argString += "false";
} else {
argString += "true";
}
nextChar();
break;
}
/* Fallthrough */
case 'j':
case 'g':
case 'e':
argString += copyUntil('$');
if (termChar != 0) {
argString += termChar;
}
break;
case 'm':
argString += copyUntil('$');
argString += "::*";
argString += copyUntil('$');
break;
default:
throw new NameError("Unknown template argument kind at " + charPtr);
}
if (thisChar() != '$') {
throw new NameError("Symbol parsing failed: arglist expected at " + charPtr);
}
charHold = nextChar();
}
if (mainArgs == true && argString.isEmpty() == false) {
printDebug("Added args " + argString);
mainArgumentsList.add(argString);
}
returnString += argString;
}
if (charHold != finishingCharacter && prevTemplClose == true) {
prevTemplClose = false;
}
} catch (NameError err) {
onErrorReturn = returnString + onErrorReturn;
throw err;
}
printDebug("End copy args");
return returnString;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment