diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/label/DemanglerCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/label/DemanglerCmd.java index 9be2c5a7d3..abb5bceefd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/label/DemanglerCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/label/DemanglerCmd.java @@ -129,7 +129,7 @@ public class DemanglerCmd extends BackgroundCommand { ". Message: " + message); } - Msg.error(this, getStatusMsg()); + Msg.error(this, getStatusMsg(), e); } public String getResult() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/AbstractDemangledFunctionDefinitionDataType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/AbstractDemangledFunctionDefinitionDataType.java index f1dabf41e0..0c8cc10813 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/AbstractDemangledFunctionDefinitionDataType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/AbstractDemangledFunctionDefinitionDataType.java @@ -18,6 +18,8 @@ package ghidra.app.util.demangler; import java.util.ArrayList; import java.util.List; +import org.apache.commons.lang3.StringUtils; + import ghidra.program.model.data.*; import ghidra.program.model.symbol.Namespace; @@ -40,9 +42,6 @@ public abstract class AbstractDemangledFunctionDefinitionDataType extends Demang protected boolean isTrailingUnaligned; protected boolean isTrailingRestrict; - /** display parens in front of parameter list */ - protected boolean displayFunctionPointerParens = true; - AbstractDemangledFunctionDefinitionDataType(String mangled, String originalDemangled) { super(mangled, originalDemangled, DEFAULT_NAME_PREFIX + nextId()); } @@ -134,10 +133,6 @@ public abstract class AbstractDemangledFunctionDefinitionDataType extends Demang isTrailingRestrict = true; } - public void setDisplayFunctionPointerParens(boolean b) { - this.displayFunctionPointerParens = b; - } - /** * Adds a parameters to the end of the parameter list for this demangled function * @param parameter the new parameter to add @@ -158,13 +153,8 @@ public abstract class AbstractDemangledFunctionDefinitionDataType extends Demang StringBuilder buffer = new StringBuilder(); StringBuilder buffer1 = new StringBuilder(); String s = getConventionPointerNameString(name); - if (s.contains(" ") || s.isEmpty()) { - // spaces--add parens - addFunctionPointerParens(buffer1, s); - } - else { // this allows the '__cdecl' in templates to not have parens - buffer1.append(s); - } + + addFunctionPointerParens(buffer1, s); buffer1.append('('); for (int i = 0; i < parameters.size(); ++i) { @@ -234,26 +224,28 @@ public abstract class AbstractDemangledFunctionDefinitionDataType extends Demang StringBuilder buffer = new StringBuilder(); buffer.append(callingConvention == null ? EMPTY_STRING : callingConvention); + StringBuilder typeBuffer = new StringBuilder(); int pointerLevels = getPointerLevels(); if (pointerLevels > 0) { - if (callingConvention != null) { - buffer.append(SPACE); - } - addParentName(buffer); + addParentName(typeBuffer); for (int i = 0; i < pointerLevels; ++i) { - buffer.append(getTypeString()); + typeBuffer.append(getTypeString()); } } - if ((modifier != null) && (modifier.length() != 0)) { - if (buffer.length() > 2) { + if (!StringUtils.isBlank(typeBuffer)) { + + if (!StringUtils.isBlank(callingConvention)) { buffer.append(SPACE); } - buffer.append(modifier); + + buffer.append(typeBuffer); } + addModifier(buffer); + if (isConstPointer) { buffer.append(CONST); } @@ -266,7 +258,7 @@ public abstract class AbstractDemangledFunctionDefinitionDataType extends Demang } if (name != null) { - if ((buffer.length() > 2) && (buffer.charAt(buffer.length() - 1) != SPACE)) { + if ((buffer.length() > 0) && (buffer.charAt(buffer.length() - 1) != SPACE)) { buffer.append(SPACE); } buffer.append(name); @@ -275,11 +267,29 @@ public abstract class AbstractDemangledFunctionDefinitionDataType extends Demang return buffer.toString(); } - protected void addFunctionPointerParens(StringBuilder buffer, String s) { - if (!displayFunctionPointerParens) { + private void addModifier(StringBuilder buffer) { + if (StringUtils.isBlank(modifier)) { return; } + // + // Guilty knowledge: in many cases the 'modifier' is the same as the type string. Further, + // when we print signatures, we will print the type string if there are pointer levels. To + // prevent duplication, do not print the modifier when it matches the type string and we + // will be printing the type string (which is printed when there are pointer levels). + // + if (modifier.equals(getTypeString()) && + getPointerLevels() > 0) { + return; + } + + if (buffer.length() > 2) { + buffer.append(SPACE); + } + buffer.append(modifier); + } + + protected void addFunctionPointerParens(StringBuilder buffer, String s) { buffer.append('(').append(s).append(')'); } @@ -310,15 +320,7 @@ public abstract class AbstractDemangledFunctionDefinitionDataType extends Demang fddt.setReturnType(returnType.getDataType(dataTypeManager)); } - if (parameters.size() != 1 || - !(parameters.get(0).getDataType(dataTypeManager) instanceof VoidDataType)) { - ParameterDefinition[] params = new ParameterDefinition[parameters.size()]; - for (int i = 0; i < parameters.size(); ++i) { - params[i] = new ParameterDefinitionImpl(null, - parameters.get(i).getDataType(dataTypeManager), null); - } - fddt.setArguments(params); - } + setParameters(fddt, dataTypeManager); DataType dt = DemangledDataType.findDataType(dataTypeManager, namespace, getName()); if (dt == null || !(dt instanceof FunctionDefinitionDataType)) { @@ -327,4 +329,27 @@ public abstract class AbstractDemangledFunctionDefinitionDataType extends Demang return new PointerDataType(dt, dataTypeManager); } + + private void setParameters(FunctionDefinitionDataType fddt, DataTypeManager dataTypeManager) { + if (hasSingleVoidParameter(dataTypeManager)) { + return; + } + + ParameterDefinition[] params = new ParameterDefinition[parameters.size()]; + for (int i = 0; i < parameters.size(); ++i) { + params[i] = new ParameterDefinitionImpl(null, + parameters.get(i).getDataType(dataTypeManager), null); + } + fddt.setArguments(params); + } + + private boolean hasSingleVoidParameter(DataTypeManager dataTypeManager) { + if (parameters.size() != 1) { + return false; + } + + DemangledDataType parameter = parameters.get(0); + DataType dt = parameter.getDataType(dataTypeManager); + return dt instanceof VoidDataType; + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledDataType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledDataType.java index 59cdfceeb9..f21fd5ae24 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledDataType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledDataType.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; + import ghidra.program.database.data.DataTypeUtilities; import ghidra.program.model.data.*; import ghidra.program.model.data.Enum; @@ -404,6 +406,9 @@ public class DemangledDataType extends DemangledType { } static Structure createPlaceHolderStructure(String dtName, Demangled namespace) { + if (StringUtils.isBlank(dtName)) { + throw new IllegalArgumentException("Name cannot be blank"); + } StructureDataType structDT = new StructureDataType(dtName, 0); structDT.setDescription("PlaceHolder Structure"); structDT.setCategoryPath(getDemanglerCategoryPath(dtName, namespace)); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunction.java index 1788468741..486f2753f0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunction.java @@ -229,25 +229,8 @@ public class DemangledFunction extends DemangledObject { buffer.append('<').append(templatedConstructorType).append('>'); } - Iterator paramIterator = parameters.iterator(); - buffer.append('('); - String pad = format ? pad(buffer.length()) : ""; - if (!paramIterator.hasNext()) { - buffer.append("void"); - } + addParameters(buffer, format); - while (paramIterator.hasNext()) { - buffer.append(paramIterator.next().getSignature()); - if (paramIterator.hasNext()) { - buffer.append(','); - if (format) { - buffer.append('\n'); - } - buffer.append(pad); - } - } - - buffer.append(')'); buffer.append(storageClass == null ? "" : " " + storageClass); if (returnType instanceof DemangledFunctionPointer) { @@ -303,6 +286,29 @@ public class DemangledFunction extends DemangledObject { return buffer.toString(); } + protected void addParameters(StringBuilder buffer, boolean format) { + Iterator paramIterator = parameters.iterator(); + buffer.append('('); + int padLength = format ? buffer.length() : 0; + String pad = StringUtils.rightPad("", padLength); + if (!paramIterator.hasNext()) { + buffer.append("void"); + } + + while (paramIterator.hasNext()) { + buffer.append(paramIterator.next().getSignature()); + if (paramIterator.hasNext()) { + buffer.append(','); + if (format) { + buffer.append('\n'); + } + buffer.append(pad); + } + } + + buffer.append(')'); + } + @Override public String getNamespaceName() { return getName() + getParameterString(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunctionIndirect.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunctionIndirect.java index 88a782f433..405cdbcc97 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunctionIndirect.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunctionIndirect.java @@ -32,4 +32,10 @@ public class DemangledFunctionIndirect extends AbstractDemangledFunctionDefiniti protected String getTypeString() { return EMPTY_STRING; } + + @Override + protected void addFunctionPointerParens(StringBuilder buffer, String s) { + // do not display pointer parens + buffer.append(s); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunctionPointer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunctionPointer.java index f6519bef42..4784e1d7da 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunctionPointer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledFunctionPointer.java @@ -20,12 +20,34 @@ package ghidra.app.util.demangler; */ public class DemangledFunctionPointer extends AbstractDemangledFunctionDefinitionDataType { + /** display parens in front of parameter list */ + private boolean displayFunctionPointerSyntax = true; + public DemangledFunctionPointer(String mangled, String originalDemangled) { super(mangled, originalDemangled); + incrementPointerLevels(); // a function pointer is 1 level by default } @Override protected String getTypeString() { return "*"; } + + /** + * Signals whether to display function pointer syntax when there is no function name, which + * is '{@code (*)}', such as found in this example '{@code void (*)()}'. the default is true + * @param b true to display nameless function pointer syntax; false to not display + */ + public void setDisplayDefaultFunctionPointerSyntax(boolean b) { + this.displayFunctionPointerSyntax = b; + } + + @Override + protected void addFunctionPointerParens(StringBuilder buffer, String s) { + if (!displayFunctionPointerSyntax) { + return; + } + + buffer.append('(').append(s).append(')'); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledLambda.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledLambda.java index 475a887428..9eab5750e5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledLambda.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledLambda.java @@ -34,4 +34,9 @@ public class DemangledLambda extends DemangledFunction { public String toString() { return getName(); } + + @Override + protected void addParameters(StringBuilder buffer, boolean format) { + // no parameter display for lambdas; the name currently shows the parameters + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledObject.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledObject.java index bdc002a5c3..2123b3cc64 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledObject.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledObject.java @@ -68,7 +68,7 @@ public abstract class DemangledObject implements Demangled { protected String basedName; protected String memberScope; - private String signature; + private String plateComment; // Status of mangled String converted successfully to demangled String private boolean demangledNameSucceeded = false; @@ -260,15 +260,6 @@ public abstract class DemangledObject implements Demangled { return getName(); } - /** - * Sets the signature. Calling this method will - * override the auto-generated signature. - * @param signature the signature - */ - public void setSignature(String signature) { - this.signature = signature; - } - @Override public String toString() { return getSignature(false); @@ -349,19 +340,29 @@ public abstract class DemangledObject implements Demangled { return true; } + /** + * Sets the plate comment to be used if the {@link #getOriginalDemangled()} string is not + * available + * + * @param plateComment the plate comment text + */ + public void setBackupPlateComment(String plateComment) { + this.plateComment = plateComment; + } + + /** + * Creates descriptive text that is intended to be used as documentation. The text defaults + * to the original demangled text. If that is not available, then any text set by + * {@link #setBackupPlateComment(String)} will be used. The last choice for this text is + * the signature generated by {@link #getSignature(boolean)}. + * + * @return the text + */ protected String generatePlateComment() { if (originalDemangled != null) { return originalDemangled; } - return (signature == null) ? getSignature(true) : signature; - } - - protected String pad(int len) { - StringBuffer buffer = new StringBuffer(); - for (int i = 0; i < len; i++) { - buffer.append(' '); - } - return buffer.toString(); + return (plateComment == null) ? getSignature(true) : plateComment; } protected Symbol applyDemangledName(Address addr, boolean setPrimary, @@ -494,7 +495,6 @@ public abstract class DemangledObject implements Demangled { NamespaceUtils.getNamespaceQualifiedName(namespace, namespaceName, false)); break; } - } return namespace; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledType.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledType.java index 6288ceab96..85f0845343 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledType.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledType.java @@ -15,6 +15,8 @@ */ package ghidra.app.util.demangler; +import org.apache.commons.lang3.StringUtils; + import ghidra.program.model.symbol.Namespace; /** @@ -53,6 +55,10 @@ public class DemangledType implements Demangled { @Override public void setName(String name) { + if (StringUtils.isBlank(name)) { + throw new IllegalArgumentException("Name cannot be blank"); + } + demangledName = name; this.name = name; if (name != null) { diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemangler.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemangler.java index 9513e94f05..3b1735927a 100644 --- a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemangler.java +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemangler.java @@ -117,9 +117,6 @@ public class GnuDemangler implements Demangler { dfunc.setNamespace(demangledObject.getNamespace()); demangledObject = dfunc; } - else { - demangledObject.setSignature(demangled); - } if (isDwarf) { DemangledAddressTable dat = diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerParser.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerParser.java index 6b64a95ae4..910607df30 100644 --- a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerParser.java +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerParser.java @@ -93,16 +93,17 @@ public class GnuDemanglerParser { /* * Sample: bob(short (&)[7]) * bob(int const[8] (*) [12]) + * _S_ref(array const (&) [64]) + * _S_ptr(Foo const* const (&) [3]) + * {lambda(long&, unsigned int)#1} const (&) [4] * - * Pattern: name[optional '*'](*|&)[optional spaces][optional value] - * + * Pattern: [optional const with optional '[',']', number, '*', '&' ] + * (*|&)[optional spaces]brackets with optional characters inside + * * Parts: - * -a word (capture group 1) - * -followed by an optional pointer '*' - * -followed by a space - * -*optional: any other text (e.g., const[8]) (non-capture group) - * -followed by '()' that contain a '&' or a '*' (capture group 2) - * -followed by one or more '[]' with optional interior text (capture group 3) + * -optional const text (e.g., const[8]) (non-capture group) + * -followed by '()' that contain a '&' or a '*' (capture group 1) + * -followed by one or more '[]' with optional interior text (capture group 2) * * Group Samples: * short (&)[7] @@ -117,7 +118,8 @@ public class GnuDemanglerParser { * */ private static final Pattern ARRAY_POINTER_REFERENCE_PATTERN = - Pattern.compile("([\\w:]+)\\*?\\s(?:.*)\\(([&*])\\)\\s*((?:\\[.*?\\])+)"); + Pattern.compile( + "\\s(?:const[\\[\\]\\d\\*&]{0,4}\\s)*\\(([&*])\\)\\s*((?:\\[.*?\\])+)"); /* * Sample: bob(short (&)[7]) @@ -309,6 +311,16 @@ public class GnuDemanglerParser { return Pattern.compile(operatorPrefix + parameters + trailing); } + /** + * Pattern to catch literal strings of the form: + * + * -1l + * 2l + * 0u + * 4294967295u + */ + private static final Pattern LITERAL_NUMBER_PATTERN = Pattern.compile("-*\\d+[ul]{0,1}"); + private String mangledSource; private String demangledSource; @@ -353,6 +365,13 @@ public class GnuDemanglerParser { return operatorHandler; } + // Note: this really is a 'special handler' check that used to be handled above. However, + // some demangled operator strings begin with this text. If we do this check above, + // then we will not correctly handle those operators. + if (mangledSource.startsWith("_ZZ")) { + return new ItemInNamespaceHandler(demangled); + } + return null; } @@ -397,9 +416,6 @@ public class GnuDemanglerParser { return new ItemInNamespaceHandler(demangled, prefix, type); } - if (mangled.startsWith("_ZZ")) { - return new ItemInNamespaceHandler(demangled); - } return null; } @@ -420,12 +436,15 @@ public class GnuDemanglerParser { // following that text in the original string. We want the name to be the full lambda // text, without spaces. // - LambdaName lambdaName = getLambdaName(demangled); + String prefix = signatureParts.getRawParameterPrefix(); + int lambdaStart = prefix.length() - LAMBDA_START.length(); // strip off '{lambda' + String lambdaText = demangled.substring(lambdaStart); + LambdaName lambdaName = getLambdaName(lambdaText); String uniqueName = lambdaName.getFullText(); String escapedLambda = removeBadSpaces(uniqueName); simpleName = simpleName.replace(LAMBDA_START, escapedLambda); function = new DemangledLambda(mangledSource, demangled, null); - function.setSignature(lambdaName.getFullText()); + function.setBackupPlateComment(lambdaName.getFullText()); } // @@ -437,16 +456,8 @@ public class GnuDemanglerParser { function.addParameter(parameter); } - // For GNU, we cannot leave the return type as null, because the DemangleCmd will fill in - // pointer to the class to accommodate windows demangling - DemangledDataType defaultReturnType = - new DemangledDataType(mangledSource, demangled, "undefined"); - function.setReturnType(defaultReturnType); - String returnType = signatureParts.getReturnType(); - if (returnType != null) { - setReturnType(function, returnType); - } + setReturnType(demangled, function, returnType); if (demangled.endsWith(CONST)) { function.setConst(true); @@ -455,27 +466,54 @@ public class GnuDemanglerParser { return function; } - private void setReturnType(DemangledFunction function, String returnType) { + private void setReturnType(String demangled, DemangledFunction function, String returnType) { - if (DECLTYPE_RETURN_TYPE_PATTERN.matcher(returnType).matches()) { + String updatedReturnType = returnType; + if (returnType != null && DECLTYPE_RETURN_TYPE_PATTERN.matcher(returnType).matches()) { // Not sure yet if there is any information we wish to recover from this pattern. // Sample: decltype (functionName({parm#1}, (float)[42c80000])) + updatedReturnType = null; + } + + if (updatedReturnType != null) { + function.setReturnType(parseReturnType(updatedReturnType)); return; } - function.setReturnType(parseReturnType(returnType)); + // For GNU, we cannot leave the return type as null, because the DemangleCmd will fill in + // pointer to the class to accommodate windows demangling + DemangledDataType defaultReturnType = + new DemangledDataType(mangledSource, demangled, "undefined"); + function.setReturnType(defaultReturnType); } private LambdaName getLambdaName(String name) { - Matcher matcher = LAMBDA_PATTERN.matcher(name); + if (!name.startsWith("{")) { + // the text must start with the lambda syntax; ignore lambdas that are internal to + // the given name + return null; + } + + // This replacement string will leave the initial 'lambda' text and replace all others + // with a placeholder value. This allows us to use a simple regex pattern when pulling + // the lambda apart. This is required to handle the case where a lambda expression + // contains a nested lambda expression. + LambdaReplacedString replacedString = new LambdaReplacedString(name); + String updatedName = replacedString.getModifiedText(); + + Matcher matcher = LAMBDA_PATTERN.matcher(updatedName); if (!matcher.matches()) { return null; } + // restore the placeholder values to get back the original lambda text String fullText = matcher.group(1); + fullText = replacedString.restoreReplacedText(fullText); String params = matcher.group(2); + params = replacedString.restoreReplacedText(params); String trailing = matcher.group(3); + trailing = replacedString.restoreReplacedText(trailing); String modifiers = matcher.group(4); return new LambdaName(fullText, params, trailing, modifiers); } @@ -513,7 +551,21 @@ public class GnuDemanglerParser { DemangledObject parent = parseFunctionOrVariable(parentText); String name = itemText.substring(pos + 2); DemangledObject item = parseFunctionOrVariable(name); - item.setNamespace(parent); + + // + // Convert the parent type to a suitable namespace. The parent type may have spaces + // in its name, which is not allowed in an applied namespace. + // + // We may eventually want to move this logic into the DemangledObject's + // createNamespace() method. This would also apply to the convertToNamespaces() + // method in this class. + // + String namespaceName = parent.getNamespaceName(); + String escapedName = removeBadSpaces(namespaceName); + DemangledType type = new DemangledType(mangledSource, demangledSource, escapedName); + type.setNamespace(parent.getNamespace()); + + item.setNamespace(type); return item; } @@ -530,6 +582,19 @@ public class GnuDemanglerParser { return condensedString.getCondensedText(); } + private String removeTrailingDereferenceCharacters(String text) { + + int i = text.length() - 1; + for (; i >= 0; i--) { + char c = text.charAt(i); + if (c == '*' || c == '&') { + continue; + } + break; + } + return text.substring(0, i + 1); + } + /** * This method separates the parameters as strings. * This is more complicated then one might initially think. @@ -670,6 +735,12 @@ public class GnuDemanglerParser { return dt; } + + // this handles the case where the demangled template has an empty argument + if ("".equals(parameter.trim())) { + return new DemangledDataType(mangledSource, demangledSource, "missing_argument"); + } + return parseDataType(parameter); } @@ -682,10 +753,16 @@ public class GnuDemanglerParser { DemangledDataType dt = createTypeInNamespace(fullDatatype); String datatype = dt.getDemangledName(); - if ("*".equals(datatype)) { + if (isMemberPointerOrReference(fullDatatype, datatype)) { return createMemberPointer(fullDatatype); } + // note: we should only encounter literals as template arguments. Function parameters + // and return types should never be literals. + if (isLiteral(fullDatatype)) { + return createLiteral(fullDatatype); + } + boolean finishedName = false; for (int i = 0; i < datatype.length(); ++i) { char ch = datatype.charAt(i); @@ -734,23 +811,37 @@ public class GnuDemanglerParser { LambdaName lambdaName = getLambdaName(datatype); - // check for array case - Matcher arrayMatcher = ARRAY_POINTER_REFERENCE_PATTERN.matcher(datatype); - if (arrayMatcher.matches()) { - Demangled namespace = dt.getNamespace(); - String name = arrayMatcher.group(1);// group 0 is the entire string - dt = parseArrayPointerOrReference(datatype, name, arrayMatcher); - dt.setNamespace(namespace); - i = arrayMatcher.end(); + // + // Check for array case + // + // remove the templates to allow us to use a simpler regex when checking for arrays + DemangledDataType newDt = tryToParseArrayPointerOrReference(dt, datatype); + if (newDt != null) { + dt = newDt; + i = datatype.length(); } + // lambda case, maybe an array else if (lambdaName != null) { - String fullText = lambdaName.getFullText(); - dt.setName(fullText); - int offset = fullText.indexOf('('); - int remaining = fullText.length() - offset; - i = i + remaining; // end of lambda's closing '}' - i = i - 1; // back up one space to catch optional templates on next loop pass + + DemangledDataType lambdaArrayDt = + tryToParseLambdaArrayPointerOrReference(lambdaName, dt, datatype); + if (lambdaArrayDt != null) { + dt = lambdaArrayDt; + i = datatype.length(); + } + else { + // try a non-array lambda + String fullText = lambdaName.getFullText(); + dt.setName(fullText); + int offset = fullText.indexOf('('); + // to to the end of the lambda, which is its length, minus our position + // inside the lambda + int remaining = fullText.length() - offset; + i = i + remaining; // end of lambda's closing '}' + i = i - 1; // back up one space to catch optional templates on next loop pass + } } + // function pointer case else { // e.g., unsigned long (*)(long const &) boolean hasPointerParens = hasConsecutiveSetsOfParens(datatype.substring(i)); @@ -856,6 +947,81 @@ public class GnuDemanglerParser { return dt; } + private DemangledDataType createLiteral(String datatype) { + + // literal cases handled: -1, -1l, -1ul + char lastChar = datatype.charAt(datatype.length() - 1); + if (lastChar == 'l') { + return new DemangledDataType(mangledSource, demangledSource, "long"); + } + + return new DemangledDataType(mangledSource, demangledSource, "int"); + } + + private boolean isLiteral(String fullDatatype) { + Matcher m = LITERAL_NUMBER_PATTERN.matcher(fullDatatype); + return m.matches(); + } + + private DemangledDataType tryToParseLambdaArrayPointerOrReference(LambdaName lambdaName, + DemangledDataType dt, String datatype) { + + // remove the lambda text to allow us to use a simpler regex when checking for arrays + String fullText = lambdaName.getFullText(); + ReplacedString lambdaString = new CustomReplacedString(datatype, fullText); + String noLambdaString = lambdaString.getModifiedText(); + + Matcher matcher = ARRAY_POINTER_REFERENCE_PATTERN.matcher(noLambdaString); + if (!matcher.find()) { + return null; + } + + int start = matcher.start(0); + String leading = noLambdaString.substring(0, start); + leading = removeTrailingDereferenceCharacters(leading); + + Demangled namespace = dt.getNamespace(); + String name = leading; + DemangledDataType newDt = parseArrayPointerOrReference(datatype, name, lambdaString, + matcher); + newDt.setNamespace(namespace); + return newDt; + } + + private DemangledDataType tryToParseArrayPointerOrReference(DemangledDataType dt, + String datatype) { + + ReplacedString templatedString = new TemplatedString(datatype); + String untemplatedDatatype = templatedString.getModifiedText(); + + Matcher matcher = ARRAY_POINTER_REFERENCE_PATTERN.matcher(untemplatedDatatype); + if (!matcher.find()) { + return null; + } + + int start = matcher.start(0); + String leading = untemplatedDatatype.substring(0, start); + leading = removeTrailingDereferenceCharacters(leading); + + Demangled namespace = dt.getNamespace(); + String name = leading; + DemangledDataType newDt = parseArrayPointerOrReference(datatype, name, templatedString, + matcher); + newDt.setNamespace(namespace); + return newDt; + } + + private boolean isMemberPointerOrReference(String fullDataType, String datatype) { + + String test = datatype; + test = test.replaceAll("const|\\*|&|\\s", ""); + if (!test.isEmpty()) { + return false; + } + + return fullDataType.endsWith(Namespace.DELIMITER + datatype); + } + private boolean hasConsecutiveSetsOfParens(String text) { int end = findBalancedEnd(text, 0, '(', ')'); if (end < -1) { @@ -869,9 +1035,12 @@ public class GnuDemanglerParser { private DemangledDataType createMemberPointer(String datatype) { // this is temp code we expect to update as more samples arrive - // example: NS1::Type1 NS1::ParenType::* - int trimLength = 3; // '::*' - String typeWithoutPointer = datatype.substring(0, datatype.length() - trimLength); + // + // Examples: + // Type NS1::Type1 NS1::ParenType::* + // Type NS1::Type1 NS1::ParenType::* const& + int namespaceEnd = datatype.lastIndexOf(Namespace.DELIMITER); + String typeWithoutPointer = datatype.substring(0, namespaceEnd); int space = typeWithoutPointer.indexOf(' '); DemangledDataType dt; if (space != -1) { @@ -906,6 +1075,7 @@ public class GnuDemanglerParser { Character.isDigit(ch) || ch == ':' || ch == '_' || + ch == '{' || ch == '$'; //@formatter:on } @@ -1114,12 +1284,16 @@ public class GnuDemanglerParser { } private DemangledDataType parseArrayPointerOrReference(String datatype, String name, - Matcher matcher) { + ReplacedString replacedString, Matcher matcher) { + // int (*)[8] // char (&)[7] + // Foo const* const (&) [3] - DemangledDataType dt = new DemangledDataType(mangledSource, demangledSource, name); - String type = matcher.group(2); + String realName = replacedString.restoreReplacedText(name); + + DemangledDataType dt = new DemangledDataType(mangledSource, demangledSource, realName); + String type = matcher.group(1); if (type.equals("*")) { dt.incrementPointerLevels(); } @@ -1130,9 +1304,29 @@ public class GnuDemanglerParser { throw new DemanglerParseException("Unexpected charater inside of parens: " + type); } - String arraySubscripts = matcher.group(3); - int n = StringUtilities.countOccurrences(arraySubscripts, '['); - dt.setArray(n); + // + // Grab the middle text, for example, inside: + // + // Foo const* const (&) [3] + // + // we would like to grab 'const* const(&)' and similar text such as 'const* const*' + // + String safeDatatype = replacedString.getModifiedText(); + int midTextStart = safeDatatype.indexOf(name) + name.length(); + int midTextEnd = matcher.start(1) - 1; // -1 for opening '(' + String midText = safeDatatype.substring(midTextStart, midTextEnd); + if (midText.contains(CONST)) { + dt.setConst(); + } + + int pointers = StringUtils.countMatches(midText, '*'); + for (int i = 0; i < pointers; i++) { + dt.incrementPointerLevels(); + } + + String arraySubscripts = matcher.group(2); + int arrays = StringUtilities.countOccurrences(arraySubscripts, '['); + dt.setArray(arrays); return dt; } @@ -1141,14 +1335,14 @@ public class GnuDemanglerParser { //unsigned long (*)(long const &) int parenStart = functionString.indexOf('('); - int parenEnd = functionString.indexOf(')'); - + int parenEnd = findBalancedEnd(functionString, parenStart, '(', ')'); String returnType = functionString.substring(0, parenStart).trim(); int paramStart = functionString.indexOf('(', parenEnd + 1); int paramEnd = functionString.lastIndexOf(')'); String parameters = functionString.substring(paramStart + 1, paramEnd); - return createFunctionPointer(parameters, returnType); + DemangledFunctionPointer dfp = createFunctionPointer(parameters, returnType); + return dfp; } private DemangledFunctionPointer parseFunction(String functionString, int offset) { @@ -1156,7 +1350,6 @@ public class GnuDemanglerParser { int parenStart = functionString.indexOf('(', offset); int parenEnd = findBalancedEnd(functionString, parenStart, '(', ')'); - //int parenEnd = functionString.indexOf(')', parenStart + 1); String returnType = functionString.substring(0, parenStart).trim(); @@ -1164,7 +1357,9 @@ public class GnuDemanglerParser { int paramEnd = parenEnd; String parameters = functionString.substring(paramStart + 1, paramEnd); DemangledFunctionPointer dfp = createFunctionPointer(parameters, returnType); - dfp.setDisplayFunctionPointerParens(false); + + // disable the function pointer display so this type reads like a function + dfp.setDisplayDefaultFunctionPointerSyntax(false); return dfp; } @@ -1324,7 +1519,6 @@ public class GnuDemanglerParser { DemangledObject doBuild(Demangled demangledObject) { DemangledFunction function = (DemangledFunction) demangledObject; - function.setSignature(type); function.setCallingConvention(CompilerSpec.CALLING_CONVENTION_thiscall); DemangledThunk thunk = new DemangledThunk(mangledSource, demangledSource, function); @@ -1545,9 +1739,14 @@ public class GnuDemanglerParser { // Ghidra does not allow spaces in the name or extra parens. So, make a name that is // as clear as possible in describing the construct. // + if (shortReturnTypeName.contains("(")) { + // assume function pointer + shortReturnTypeName = "function.pointer"; + } + method.setName("operator.cast.to." + shortReturnTypeName); - method.setSignature(fullName + " " + fullReturnType); + method.setBackupPlateComment(fullName + " " + fullReturnType + "()"); method.setOverloadedOperator(true); return method; @@ -1604,10 +1803,9 @@ public class GnuDemanglerParser { if (arrayBrackets != null) { name += "[]"; } + function.setName("operator." + name); - - function.setSignature(operatorText + " " + operatorName); - + function.setBackupPlateComment(operatorText + " " + operatorName); return function; } } @@ -1628,6 +1826,7 @@ public class GnuDemanglerParser { paramEnd = -1; return; } + paramStart = findParameterStart(text, paramEnd); int templateEnd = findTemplateEnd(text, 0); int templateStart = -1; @@ -1727,6 +1926,7 @@ public class GnuDemanglerParser { private String returnType; private String name; + private String rawParameterPrefix; private List parameters; @@ -1746,9 +1946,9 @@ public class GnuDemanglerParser { // 'prefix' is the text before the parameters int prefixEndPos = paramStart; - String rawPrefix = signatureString.substring(0, prefixEndPos).trim(); + rawParameterPrefix = signatureString.substring(0, prefixEndPos).trim(); - CondensedString prefixString = new CondensedString(rawPrefix); + CondensedString prefixString = new CondensedString(rawParameterPrefix); String prefix = prefixString.getCondensedText(); int nameStart = Math.max(0, prefix.lastIndexOf(' ')); name = prefix.substring(nameStart, prefix.length()).trim(); @@ -1767,6 +1967,11 @@ public class GnuDemanglerParser { return name; } + // this is the original demangled text up to, but excluding, the parameters + String getRawParameterPrefix() { + return rawParameterPrefix; + } + boolean isValidFunction() { return isFunction; } @@ -1880,4 +2085,149 @@ public class GnuDemanglerParser { } } } + + /** + * A class that allows us to pass around string content that has had some of its text + * replaced with temporary values. Clients can also use this class to get back the original + * text. + */ + private abstract class ReplacedString { + + static final String PLACEHOLDER = "REPLACEDSTRINGTEMPNAMEPLACEHOLDERVALUE"; + + @SuppressWarnings("unused") // used by toString() + private String sourceText; + + ReplacedString(String sourceText) { + this.sourceText = sourceText; + } + + abstract String restoreReplacedText(String modifiedText); + + abstract String getModifiedText(); + + @Override + public String toString() { + return Json.toString(this); + } + } + + /** + * A string that clients can use to replace specific text patterns + */ + private class CustomReplacedString extends ReplacedString { + + private String placeholderText = getClass().getSimpleName().toUpperCase() + PLACEHOLDER; + private String replacedText; + private String modifiedText; + + CustomReplacedString(String input, String textToReplace) { + super(input); + this.replacedText = textToReplace; + this.modifiedText = input.replace(textToReplace, placeholderText); + } + + @Override + String restoreReplacedText(String mutatedText) { + return mutatedText.replace(placeholderText, replacedText); + } + + @Override + String getModifiedText() { + return modifiedText; + } + } + + /** + * A simple class to replace templates with a temporary placeholder value + */ + private class TemplatedString extends ReplacedString { + + private String placeholderText = getClass().getSimpleName().toUpperCase() + PLACEHOLDER; + + private String replacedText; + private String modifiedText; + + TemplatedString(String input) { + super(input); + replaceTemplates(input); + } + + private void replaceTemplates(String string) { + StringBuilder buffy = new StringBuilder(); + StringBuilder templateBuffer = new StringBuilder(); + int depth = 0; + for (int i = 0; i < string.length(); i++) { + char c = string.charAt(i); + if (c == '<') { + if (depth == 0) { + buffy.append(placeholderText); + } + + templateBuffer.append(c); + depth++; + continue; + } + else if (c == '>') { + templateBuffer.append(c); + depth--; + continue; + } + + if (depth == 0) { + buffy.append(c); + } + else { + templateBuffer.append(c); + } + } + + modifiedText = buffy.toString(); + replacedText = templateBuffer.toString(); + } + + @Override + String restoreReplacedText(String s) { + return s.replace(placeholderText, replacedText); + } + + @Override + String getModifiedText() { + return modifiedText; + } + } + + /** + * A simple class to replace the text 'lambda' with a temporary placeholder value + */ + private class LambdaReplacedString extends ReplacedString { + + private String placeholderText = getClass().getSimpleName().toUpperCase() + PLACEHOLDER; + private String modifiedText; + + LambdaReplacedString(String input) { + super(input); + + StringBuilder buffer = new StringBuilder(); + Pattern p = Pattern.compile(LAMBDA); + Matcher matcher = p.matcher(input); + matcher.find(); // keep the first match + while (matcher.find()) { + matcher.appendReplacement(buffer, placeholderText); + } + matcher.appendTail(buffer); + modifiedText = buffer.toString(); + } + + @Override + String restoreReplacedText(String s) { + return s.replaceAll(placeholderText, LAMBDA); + } + + @Override + String getModifiedText() { + return modifiedText; + } + + } } diff --git a/Ghidra/Features/GnuDemangler/src/test/java/ghidra/app/util/demangler/GnuDemanglerParserTest.java b/Ghidra/Features/GnuDemangler/src/test/java/ghidra/app/util/demangler/GnuDemanglerParserTest.java index 17d5ae7b65..a67b90ef58 100644 --- a/Ghidra/Features/GnuDemangler/src/test/java/ghidra/app/util/demangler/GnuDemanglerParserTest.java +++ b/Ghidra/Features/GnuDemangler/src/test/java/ghidra/app/util/demangler/GnuDemanglerParserTest.java @@ -42,6 +42,12 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { // bob(int const[8] (*) [12]) + // + // Note: it is not clear whether the this demangled string is valid modern demangler output. + // We are creating a constant array pointer from this construct, but is this what + // we should be creating? + // + String demangled = "bob(int const[8] (*) [12])"; DemangledObject object = parser.parse("fake", demangled); assertType(object, DemangledFunction.class); @@ -52,7 +58,27 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { assertEquals(1, parameters.size()); DemangledDataType p1 = parameters.get(0); assertEquals("bob(int const[8] (*) [12])", p1.getOriginalDemangled()); - assertEquals("undefined bob(int *[])", object.getSignature(false)); + assertEquals("undefined bob(int const *[])", object.getSignature(false)); + } + + @Test + public void testParse_ArrayPointerReferencePattern_ConstPointerToArrayReference() + throws Exception { + + // _S_ptr(entt::sparse_set const * &[]) + + String mangled = + "_ZNSt14__array_traitsIPKN4entt10sparse_setI8EntityIdEELm3EE6_S_ptrERA3_KS5_"; + String demangled = process.demangle(mangled); + + DemangledObject object = parser.parse(mangled, demangled); + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String signature = object.getSignature(false); + assertEquals( + "undefined std::__array_traitsconst*,3ul>::_S_ptr(entt::sparse_set const * &[])", + signature); } @Test @@ -122,14 +148,14 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { assertEquals( "undefined XpsMap::XpsMap(" + - "unsigned long ()(long const &),unsigned long,unsigned long,float)", + "unsigned long (*)(long const &),unsigned long,unsigned long,float)", object.getSignature(false)); DemangledFunction method = (DemangledFunction) object; List parameters = method.getParameters(); assertEquals(4, parameters.size()); - assertEquals("unsigned long ()(long const &)", parameters.get(0).getSignature()); + assertEquals("unsigned long (*)(long const &)", parameters.get(0).getSignature()); assertEquals("unsigned long", parameters.get(1).getSignature()); assertEquals("unsigned long", parameters.get(2).getSignature()); assertEquals("float", parameters.get(3).getSignature()); @@ -182,7 +208,7 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { "__gnu_cxx::__normal_iterator *,std::vector,std::allocator>>>", parameters.get(1).toString()); assertEquals( - "bool ()(std::pair const &,std::pair const &)", + "bool (*)(std::pair const &,std::pair const &)", parameters.get(2).toString()); assertType(parameters.get(2), DemangledFunctionPointer.class); @@ -334,6 +360,64 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { object.getSignature(false)); } + @Test + public void testParse_DefaultArg() throws Exception { + + // + // The demangled string contains this string: {default arg#1} + // + String mangled = + "_ZZN12PackManifest18CapabilityRegistry18registerCapabilityEN3gsl17basic_string_spanIKcLln1EEEbSt8functionIFbRS_R10PackReportbEEEd_NKUlS6_S8_bE_clES6_S8_b"; + + String demangled = process.demangle(mangled); + + DemangledObject object = parser.parse(mangled, demangled); + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String signature = object.getSignature(false); + assertEquals( + "PackManifest::CapabilityRegistry::registerCapability(gsl::basic_string_span,bool,std::function)::{default arg#1}::{lambda(PackManifest&,PackReport&,bool)#1}::operator()(PackManifest &,PackReport &,bool)", + signature); + } + + @Test + public void testParse_UnnamedType() throws Exception { + + // + // The demangled string contains this string: {unnamed_type#1} + // + String mangled = + "_ZN14GoalDefinitionUt_aSERKS0_"; + + String demangled = process.demangle(mangled); + + DemangledObject object = parser.parse(mangled, demangled); + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String signature = object.getSignature(false); + assertEquals( + "undefined GoalDefinition::{unnamed_type#1}::operator=({unnamed const &)", + signature); + } + + @Test + public void testParse_DecltypeAuto() throws Exception { + + String mangled = + "_Z9enum_castIN17FurnaceBlockActorUt_EEDcT_"; + + String demangled = process.demangle(mangled); + + DemangledObject object = parser.parse(mangled, demangled); + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String signature = object.getSignature(false); + assertEquals("decltype (auto)", signature); + } + @Test public void testMethod() throws Exception { String mangled = "_ZN3Foo7getBoolEf"; @@ -416,9 +500,9 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { DemangledObject object = parser.parse(mangled, demangled); assertType(object, DemangledVariable.class); - assertName(object, "dotdot", "KSimpleFileFilter", "passesFilter(KFileItem const *)"); + assertName(object, "dotdot", "KSimpleFileFilter", "passesFilter(KFileItem_const*)"); - assertEquals("KSimpleFileFilter::passesFilter(KFileItem const *)::dotdot", + assertEquals("KSimpleFileFilter::passesFilter(KFileItem_const*)::dotdot", object.getSignature(false)); } @@ -612,9 +696,9 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { DemangledObject object = parser.parse(mangled, demangled); assertType(object, DemangledVariable.class); - assertName(object, "dialog", "DDDSaveOptionsCB(_WidgetRec *,void *,void *)"); + assertName(object, "dialog", "DDDSaveOptionsCB(_WidgetRec*,void*,void*)"); - assertEquals("DDDSaveOptionsCB(_WidgetRec *,void *,void *)::dialog", + assertEquals("DDDSaveOptionsCB(_WidgetRec*,void*,void*)::dialog", object.getSignature(false)); } @@ -682,8 +766,7 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { assertType(object, DemangledFunction.class); assertName(object, "graphNew", "Layout"); - // note: the two pointers were condensed to one (I think this is correct, but not sure) - assertEquals("undefined Layout::graphNew(_GRAPH *[],char *)", object.getSignature(false)); + assertEquals("undefined Layout::graphNew(_GRAPH * *[],char *)", object.getSignature(false)); } @Test @@ -913,7 +996,7 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { assertEquals("operator<<", name); assertName(object, "operator<<", "std", "basic_ostream>"); assertEquals("undefined std::basic_ostream>" + "::operator<<(" + - "std::basic_ostream> & ()(std::basic_ostream> &))", + "std::basic_ostream> & (*)(std::basic_ostream> &))", object.getSignature()); } @@ -979,6 +1062,128 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { assertEquals("Magick::Image &", parameters.get(0).getSignature()); } + @Test + public void testPointerToArray_WithLambda() throws Exception { + + String mangled = + "_ZNSt14__array_traitsIN12LayerDetails15RandomProviderTIZNKS0_9LayerBase10initRandomEllEUlRljE_EELm4EE6_S_refERA4_KS5_m"; + + String demangled = process.demangle(mangled); + + DemangledObject object = parser.parse(mangled, demangled); + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String signature = object.getSignature(false); + assertEquals( + "undefined std::__array_traits,4ul>::_S_ref(LayerDetails::LayerBase::initRandom(long,long) const::{lambda(long&, unsigned int)#1} const &[],unsigned long)", + signature); + } + + @Test + public void testOperator_WithTemplatesMissingATemplateArgument() throws Exception { + + /* + + Note: the empty template type: '<, std...' + <, std::__cxx11::basic_string, std::allocator>> + + + std::__cxx11::basic_string, std::allocator > std::_Bind, std::allocator > (EduAppConfigs::*(EduAppConfigs const*))() const>::operator()<, std::__cxx11::basic_string, std::allocator > >() + + */ + String mangled = + "_ZNSt5_BindIFM13EduAppConfigsKFNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEvEPKS0_EEclIJES6_EET0_DpOT_"; + + String demangled = process.demangle(mangled); + + DemangledObject object = parser.parse(mangled, demangled); + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String signature = object.getSignature(false); + assertEquals( + "std::__cxx11::basic_string,std::allocator> std::_Bind,std::allocator>(EduAppConfigs::*(EduAppConfigs_const*))()const>::operator(),std::allocator>>(void)", + signature); + } + + @Test + public void testParamegterWithTemplateValue_DataTypeLiteral_int() throws Exception { + + String mangled = "_Z13reverse_rangeIjLin1EE5RangeIiXT0_EET_"; + + String demangled = process.demangle(mangled); + + DemangledObject object = parser.parse(mangled, demangled); + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String signature = object.getSignature(false); + assertEquals("Range reverse_range(unsigned int)", signature); + } + + @Test + public void testParamegterWithTemplateValue_DataTypeLiteral_long() throws Exception { + + String mangled = + "_ZN3gsl9to_stringIKcLln1EEENSt7__cxx1112basic_stringINSt12remove_constIT_E4typeESt11char_traitsIS7_ESaIS7_EEENS_17basic_string_spanIS5_XT0_EEE"; + + String demangled = process.demangle(mangled); + + DemangledObject object = parser.parse(mangled, demangled); + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String signature = object.getSignature(false); + assertEquals( + "std::__cxx11::basic_string::type,std::char_traits::type>,std::allocator::type>> gsl::to_string(gsl::basic_string_span)", + signature); + } + + @Test + public void testLambdaWithLambdaParameters() throws Exception { + + /* + + lambda contents - lambdas in templates and as a parameter + + bool (*** + const* std:: + __addressof< + Bedrock:: + Threading:: + TLSDetail:: + DefaultConstructor:: + create():: + {lambda(bool (*** const)(AssertHandlerContext const&))#1} + > + ( + Bedrock:: + Threading:: + TLSDetail:: + DefaultConstructor:: + create():: + {lambda(bool (*** const&)(AssertHandlerContext const&))#1} + ) + )(AssertHandlerContext const&) + + */ + + String mangled = + "_ZSt11__addressofIKZN7Bedrock9Threading9TLSDetail18DefaultConstructorIPPFbRK20AssertHandlerContextEvE6createEvEUlPS9_E_EPT_RSE_"; + + String demangled = process.demangle(mangled); + + DemangledObject object = parser.parse(mangled, demangled); + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String signature = object.getSignature(false); + assertEquals( + "undefined Bedrock::Threading::TLSDetail::DefaultConstructor::create()::{lambda(bool(***const*std::__addressof::create()::{lambda(bool(***const)(AssertHandlerContext_const&))#1}>(Bedrock::Threading::TLSDetail::DefaultConstructor::create()::{lambda(bool(***const&)(AssertHandlerContext_const&))#1}))(AssertHandlerContext_const&))#1}", + signature); + } + @Test public void testOperatorCastTo() throws Exception { // @@ -998,6 +1203,53 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { assertEquals("bool std::integral_constant::operator.cast.to.bool(void)", signature); } + @Test + public void testOperatorCastTo_FunctionPointer() throws Exception { + + String mangled = + "_ZZNK4entt14basic_registryI8EntityIdE6assureI32FilteredTransformationAttributesI26PreHillsEdgeTransformationEEERKNS2_12pool_handlerIT_EEvENKUlRNS_10sparse_setIS1_EERS2_S1_E_cvPFvSE_SF_S1_EEv"; + String demangled = process.demangle(mangled); + + /* + + Full demangled: + + Operator Text + + entt:: + basic_registry:: + assure >() const:: + {lambda(entt::sparse_set&, entt::basic_registry&, EntityId)#1}:: + operator void (*)(entt::sparse_set&, entt::basic_registry&, EntityId)() const + + Operartor Without Namespace + + operator void (*)(entt::sparse_set&, entt::basic_registry&, EntityId)() + + Simplified Cast Operator Construct + + operator void (*)(A,B,C)() + + */ + + DemangledObject object = parser.parse(mangled, demangled); + assertNotNull(object); + assertType(object, DemangledFunction.class); + + //@formatter:off + String expected = + "void (* " + + "entt::" + + "basic_registry::" + + "assure() const::" + + "{lambda(entt::sparse_set&,entt::basic_registry&,EntityId)#1}::" + + "operator.cast.to.function.pointer(void)" + + ")(entt::sparse_set &,entt::basic_registry &,EntityId)"; + //@formatter:on + String signature = object.getSignature(false); + assertEquals(expected, signature); + } + @Test public void testConversionOperator() throws Exception { @@ -1226,7 +1478,7 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { assertName(object, "FTransferGvmlData", "Dr", "ClipboardHelper"); assertEquals( - "undefined Dr::ClipboardHelper::FTransferGvmlData(Art::Transaction &,Ofc::TReferringPtr const &,bool,Ofc::TCntPtr,Dr::IClientDataCreator &,Ofc::TVector,0u,4294967295u> &,Art::Rect64 &)", + "undefined Dr::ClipboardHelper::FTransferGvmlData(Art::Transaction &,Ofc::TReferringPtr const &,bool,Ofc::TCntPtr,Dr::IClientDataCreator &,Ofc::TVector,int,int> &,Art::Rect64 &)", object.getSignature(false)); } @@ -1549,7 +1801,7 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { String signature = object.getSignature(false); assertEquals( - "WebCore::JSConverter>::convert(JSC::ExecState&,WebCore::JSDOMGlobalObject&,WTF::Variantconst&)::{lambda(auto:1&&)#1} brigand::for_each_args>::convert(JSC::ExecState&,WebCore::JSDOMGlobalObject&,WTF::Variantconst&)::{lambda(auto:1&&)#1},brigand::type_>,WebCore::JSConverter>::convert(JSC::ExecState&,WebCore::JSDOMGlobalObject&,WTF::Variantconst&)::{lambda(auto:1&&)#1}>,WebCore::JSConverter>::convert(JSC::ExecState&,WebCore::JSDOMGlobalObject&,WTF::Variantconst&)::{lambda(auto:1&&)#1}>>(WebCore::JSConverter>::convert(JSC::ExecState&,WebCore::JSDOMGlobalObject&,WTF::Variantconst&)::{lambda(auto:1&&)#1},brigand::type_> &&,WebCore::JSConverter>::convert(JSC::ExecState&,WebCore::JSDOMGlobalObject&,WTF::Variantconst&)::{lambda(auto:1&&)#1}> &&,WebCore::JSConverter>::convert(JSC::ExecState&,WebCore::JSDOMGlobalObject&,WTF::Variantconst&)::{lambda(auto:1&&)#1}> &&)", + "WebCore::JSConverter>::convert(JSC::ExecState&,WebCore::JSDOMGlobalObject&,WTF::Variantconst&)::{lambda(auto:1&&)#1} brigand::for_each_args>::convert(JSC::ExecState&,WebCore::JSDOMGlobalObject&,WTF::Variantconst&)::{lambda(auto:1&&)#1},brigand::type_>,WebCore::JSConverter>::convert(JSC::ExecState&,WebCore::JSDOMGlobalObject&,WTF::Variantconst&)::{lambda(auto:1&&)#1}>,WebCore::JSConverter>::convert(JSC::ExecState&,WebCore::JSDOMGlobalObject&,WTF::Variantconst&)::{lambda(auto:1&&)#1}>>(WebCore::JSConverter>::convert(JSC::ExecState&,WebCore::JSDOMGlobalObject&,WTF::Variantconst&)::{lambda(auto:1&&)#1},brigand::type_> &&,WebCore::JSConverter>::convert(JSC::ExecState&,WebCore::JSDOMGlobalObject&,WTF::Variantconst&)::{lambda(auto:1&&)#1}> &&,WebCore::JSConverter>::convert(JSC::ExecState&,WebCore::JSDOMGlobalObject&,WTF::Variantconst&)::{lambda(auto:1&&)#1}> &&)", signature); } @@ -1587,7 +1839,46 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { String signature = object.getSignature(false); assertEquals( - "undefined WebCore::FontSelectionAlgorithm::filterCapability(bool *,WebCore::FontSelectionAlgorithm::DistanceResult ()(WebCore::FontSelectionCapabilities) const,WebCore::FontSelectionCapabilities::FontSelectionRange *)", + "undefined WebCore::FontSelectionAlgorithm::filterCapability(bool *,WebCore::FontSelectionAlgorithm::DistanceResult (*)(WebCore::FontSelectionCapabilities) const,WebCore::FontSelectionCapabilities::FontSelectionRange *)", + signature); + } + + @Test + public void testFunctionParameterWithMemberPointer_ToFloat() throws Exception { + + // + // Test to ensure proper handling of 'float AvoidBlockGoal::Definition::* const&' + // which is a const reference to a floating point member of the class + // AvoidBlockGoal::Definition + // + + /* + + Demangled: + + auto && JsonUtil:: + addMember>,AvoidBlockGoal::Definition,float> + ( + + std::shared_ptr>, + float AvoidBlockGoal::Definition::*, + char const *, + float AvoidBlockGoal::Definition::* const& + + ) + + */ + String mangled = + "_ZN8JsonUtil9addMemberISt10shared_ptrINS_20JsonSchemaObjectNodeINS_10EmptyClassEN14AvoidBlockGoal10DefinitionEEEES5_fEEODaT_MT0_T1_PKcRKSC_"; + String demangled = process.demangle(mangled); + + DemangledObject object = parser.parse(mangled, demangled); + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String signature = object.getSignature(false); + assertEquals( + "auto && JsonUtil::addMember>,AvoidBlockGoal::Definition,float>(std::shared_ptr>,AvoidBlockGoal::Definition::float *,char const *,AvoidBlockGoal::Definition::float *)", signature); } @@ -1618,7 +1909,7 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { String signature = object.getSignature(false); assertEquals( - "undefined WebCore::TextCodecICU::registerCodecs(void ()(char const *,WTF::Function> ()> &&))", + "undefined WebCore::TextCodecICU::registerCodecs(void (*)(char const *,WTF::Function> ()> &&))", signature); } @@ -1676,6 +1967,23 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { } + @Test + public void testOperator_ArrayReference() throws Exception { + String mangled = + "_ZN12LayerDetails15RandomProviderTIZNKS_9LayerBase10initRandomEllEUlRljE_EclIiLm2EEET_RAT0__KS6_"; + + String demangled = process.demangle(mangled); + + DemangledObject object = parser.parse(mangled, demangled); + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String signature = object.getSignature(false); + assertEquals( + "int LayerDetails::RandomProviderT::operator()(LayerDetails::RandomProviderT::operator() const &[])", + signature); + } + @Test public void testLambdaWithTemplates() throws Exception { diff --git a/Ghidra/Features/MicrosoftDmang/src/test/java/mdemangler/MDMangBaseTest.java b/Ghidra/Features/MicrosoftDmang/src/test/java/mdemangler/MDMangBaseTest.java index 14dcde4753..8974827dc6 100644 --- a/Ghidra/Features/MicrosoftDmang/src/test/java/mdemangler/MDMangBaseTest.java +++ b/Ghidra/Features/MicrosoftDmang/src/test/java/mdemangler/MDMangBaseTest.java @@ -476,6 +476,15 @@ public class MDMangBaseTest extends AbstractGenericTest { demangleAndTest(); } + @Test + public void testFunctionPointer_NamedFunctionPointerWithAnonymousFunctionPointerParameter() + throws Exception { + mangled = "?fun@@3P6KXP6KXH@Z@ZA"; + msTruth = "void (* fun)(void (*)(int))"; + mdTruth = msTruth; + demangleAndTest(); + } + @Test public void testFunctionPointer_EMod_invalid() throws Exception { mangled = "?fn@@3PE6AHH@ZA"; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolManager.java index 52a132ca43..9d27a80cd9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolManager.java @@ -2312,7 +2312,7 @@ public class SymbolManager implements SymbolTable, ManagerDB { SourceType originalSource = namespaceSymbol.getSource(); // no duplicate check, since this class name will be set to that of the existing namespace - String tempName = name + System.nanoTime(); + String tempName = "_temp_" + System.nanoTime(); SymbolDB classSymbol = doCreateSpecialSymbol(Address.NO_ADDRESS, tempName, namespace.getParentNamespace(), SymbolType.CLASS, -1, -1, null, originalSource, false /*check for duplicate */); @@ -2334,7 +2334,8 @@ public class SymbolManager implements SymbolTable, ManagerDB { return classNamespace; } catch (DuplicateNameException | InvalidInputException | CircularDependencyException e) { - throw new AssertException("Unexpected exception creating class from namespace", e); + throw new AssertException("Unexpected exception creating class from namespace: " + + e.getMessage(), e); } finally { lock.release();