diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/CreateThunkFunctionCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/CreateThunkFunctionCmd.java index 8737cd9da4..ef3430330a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/CreateThunkFunctionCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/CreateThunkFunctionCmd.java @@ -450,8 +450,9 @@ public class CreateThunkFunctionCmd extends BackgroundCommand { BigInteger value = context.getValue(isaModeSwitchRegister, false); if (value != null && program.getListing().getInstructionAt(addr) == null) { try { - program.getProgramContext().setValue(isaModeRegister, addr, addr, - value); + program.getProgramContext() + .setValue(isaModeRegister, addr, addr, + value); } catch (ContextChangeException e) { // ignore @@ -641,7 +642,6 @@ public class CreateThunkFunctionCmd extends BackgroundCommand { return null; } try { - Msg.debug(CreateFunctionCmd.class, "Creating external function symbol: " + s.getName()); ExternalManager extMgr = program.getExternalManager(); ExternalLocation extLoc = extMgr.addExtFunction(Library.UNKNOWN, s.getName(), null, s.getSource()); 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 612a6fcfa3..9be2c5a7d3 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 @@ -80,7 +80,8 @@ public class DemanglerCmd extends BackgroundCommand { return true; // no real error } - setStatusMsg("Unable to demangle symbol: " + mangled + ". Message: " + e.getMessage()); + setStatusMsg("Unable to demangle symbol: " + mangled + " at " + addr + ". Message: " + + e.getMessage()); return false; // error // This produces too many messages for non-demangled symbols. If we could @@ -91,7 +92,8 @@ public class DemanglerCmd extends BackgroundCommand { } catch (Exception e) { // Demangler IndexOutOfBoundsException that we're not sure how to fix - setStatusMsg("Unable to demangle symbol: " + mangled + ". Message: " + e.getMessage()); + setStatusMsg("Unable to demangle symbol: " + mangled + " at " + addr + ". Message: " + + e.getMessage()); return false; } @@ -111,7 +113,7 @@ public class DemanglerCmd extends BackgroundCommand { } setStatusMsg( - "Failed to apply mangled symbol at " + addr.toString() + "; name: " + mangled + " (" + + "Failed to apply mangled symbol at " + addr + "; name: " + mangled + " (" + demangler.getClass().getName() + "/" + demangledObject.getClass().getName() + ")"); return false; // error } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java index 9e1d2f19c9..6722672bfa 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java @@ -77,7 +77,7 @@ public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer { Address address = symbol.getAddress(); String mangled = cleanSymbol(address, symbol.getName()); - DemangledObject demangled = demangle(mangled, options, log); + DemangledObject demangled = demangle(mangled, address, options, log); if (demangled != null) { apply(program, address, demangled, options, log, monitor); } @@ -172,11 +172,13 @@ public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer { * handled. * * @param mangled the mangled string + * @param address the symbol address * @param options the demangler options * @param log the error log * @return the demangled object; null if unsuccessful */ - protected DemangledObject demangle(String mangled, DemanglerOptions options, MessageLog log) { + protected DemangledObject demangle(String mangled, Address address, DemanglerOptions options, + MessageLog log) { DemangledObject demangled = null; try { @@ -192,7 +194,8 @@ public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer { } log.appendMsg(getName(), - "Unable to demangle symbol: " + mangled + ". Message: " + e.getMessage()); + "Unable to demangle symbol: " + mangled + " at " + address + ". Message: " + + e.getMessage()); return null; } 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 86cd6c4f9c..59cdfceeb9 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 @@ -90,6 +90,7 @@ public class DemangledDataType extends DemangledType { private boolean isEnum; private boolean isPointer64; private boolean isReference; + private boolean isRValueReference; private boolean isSigned; private boolean isStruct; private boolean isTemplate; @@ -445,6 +446,13 @@ public class DemangledDataType extends DemangledType { isReference = true; } + /** + * rvalue reference; C++11 + */ + public void setRValueReference() { + isRValueReference = true; + } + public void setSigned() { isSigned = true; } @@ -671,6 +679,9 @@ public class DemangledDataType extends DemangledType { if (isReference) { buffer.append(SPACE + REF_NOTATION); + if (isRValueReference) { + buffer.append(REF_NOTATION); // && + } } // the order of __ptr64 and __restrict can vary--with fuzzing... @@ -703,5 +714,4 @@ public class DemangledDataType extends DemangledType { public String toString() { return getSignature(); } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledThunk.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledThunk.java index 3497838956..94c1917b5d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledThunk.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledThunk.java @@ -25,6 +25,7 @@ import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.symbol.*; import ghidra.util.Msg; import ghidra.util.task.TaskMonitor; +import utility.function.Dummy; public class DemangledThunk extends DemangledObject { @@ -176,8 +177,7 @@ public class DemangledThunk extends DemangledObject { } Symbol s = SymbolUtilities.getExpectedLabelOrFunctionSymbol(program, - mangled, err -> Msg.warn(this, err)); - + mangled, Dummy.consumer()); if (s == null) { Address thunkedAddr = CreateThunkFunctionCmd.getThunkedAddr(program, thunkAddress, false); @@ -185,11 +185,13 @@ public class DemangledThunk extends DemangledObject { s = program.getSymbolTable().getPrimarySymbol(thunkedAddr); } } + if (s == null || !block.contains(s.getAddress())) { + Msg.warn(this, "Unable to find or create thunk for " + mangled + " at " + thunkAddress); return null; } - Address addr = s.getAddress(); + Address addr = s.getAddress(); DemanglerOptions subOptions = new DemanglerOptions(options); subOptions.setApplySignature(true); 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 416f77bfcc..0a8e286903 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 @@ -26,10 +26,11 @@ import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import generic.json.Json; -import ghidra.app.util.SymbolPath; +import ghidra.app.util.SymbolPathParser; import ghidra.app.util.demangler.*; import ghidra.program.model.lang.CompilerSpec; import ghidra.program.model.symbol.Namespace; +import ghidra.util.Msg; import ghidra.util.StringUtilities; public class GnuDemanglerParser { @@ -42,11 +43,16 @@ public class GnuDemanglerParser { private static final String TYPEINFO_FOR = "typeinfo for "; private static final String COVARIANT_RETURN_THUNK = "covariant return thunk"; - private static final Set ADDRESS_TABLE_PREFIXES = - Set.of(CONSTRUCTION_VTABLE_FOR, VTT_FOR, VTABLE_FOR, TYPEINFO_FN_FOR, TYPEINFO_FOR); + private static final Set ADDRESS_TABLE_PREFIXES = Set.of( + CONSTRUCTION_VTABLE_FOR, + VTT_FOR, + VTABLE_FOR, + TYPEINFO_FN_FOR, + TYPEINFO_FOR); private static final String OPERATOR = "operator"; private static final String LAMBDA = "lambda"; + private static final String LAMBDA_START = "{lambda"; private static final String VAR_ARGS = "..."; private static final String THUNK = "thunk"; private static final String CONST = " const"; @@ -59,8 +65,8 @@ public class GnuDemanglerParser { * Pattern: name(([const] [params])) * * Parts: -optional spaces - * -optional (const) (non-capture group) - * -followed by '()' with optinal parameter text (capture group 1) + * -optional (const) (non-capture group) + * -followed by '()' with optional parameter text (capture group 1) * * Note: this pattern is used for matching the arguments string, in the above examples it * would be: @@ -72,6 +78,22 @@ public class GnuDemanglerParser { private static final Pattern UNNECESSARY_PARENS_PATTERN = Pattern.compile("\\s*(?:const){0,1}\\((.*)\\)\\s*"); + /** + * Captures the contents of a varargs parameter that is inside of parentheses. + * + * Sample: (NS1::type&&)... + * + * Pattern: (namespace::name[modifiers])... + * + * Parts: -open paren + * -contents (capture group 1) + * -close paren + * -varargs + * + */ + private static final Pattern VARARGS_IN_PARENS = + Pattern.compile("\\((.*)\\)" + Pattern.quote("...")); + /* * Sample: bob(short (&)[7]) * bob(int const[8] (*) [12]) @@ -191,16 +213,19 @@ public class GnuDemanglerParser { * Pattern for newer C++ lambda syntax: * * Sample: {lambda(void const*, unsigned int)#1} + * {lambda(NS1::Class1 const&, int, int)#1} const& + * {lambda(auto:1&&)#1}&& * - * Pattern: [optional text] brace lambda([parameters])#digits brace + * Pattern: [optional text] brace lambda([parameters])#digits brace [trailing text] * * Parts: * -full text without leading characters (capture group 1) * -parameters of the lambda function (capture group 2) * -trailing id (capture group 3) + * -trailing modifiers (e.g., const, &, templates) (capture group 4) */ private static final Pattern LAMBDA_PATTERN = - Pattern.compile(".*(\\{" + LAMBDA + "\\((.*)\\)(#\\d+)\\})"); + Pattern.compile(".*(\\{" + LAMBDA + "\\((.*)\\)(#\\d+)\\})(.*)"); /* * Sample: {unnamed type#1} @@ -279,6 +304,7 @@ public class GnuDemanglerParser { String extra = userDefinedLiteral; alternated += '|' + extra; + // note: this capture group seems to fail with excessive templating String operatorTemplates = "(<.+>){0,1}"; String operatorPrefix = ".*(.*" + OPERATOR + "(" + alternated + ")\\s*" + operatorTemplates + ".*)\\s*"; @@ -392,11 +418,17 @@ public class GnuDemanglerParser { DemangledFunction function = new DemangledFunction(mangledSource, demangled, null); String simpleName = signatureParts.getName(); - LambdaName lambdaName = getLambdaName(demangled); - if (lambdaName != null) { + + if (simpleName.endsWith(LAMBDA_START)) { + // + // For lambdas, the signature parser will set the name to '{lambda', with the parameters + // following that text in the original string. We want the name to be the full lambda + // text, without spaces. + // + LambdaName lambdaName = getLambdaName(demangled); String uniqueName = lambdaName.getFullText(); String escapedLambda = removeBadSpaces(uniqueName); - simpleName = simpleName.replace("{lambda", escapedLambda); + simpleName = simpleName.replace(LAMBDA_START, escapedLambda); function = new DemangledLambda(mangledSource, demangled, null); function.setSignature(lambdaName.getFullText()); } @@ -436,10 +468,11 @@ public class GnuDemanglerParser { return; } - function.setReturnType(parseDataType(returnType)); + function.setReturnType(parseReturnType(returnType)); } private LambdaName getLambdaName(String name) { + Matcher matcher = LAMBDA_PATTERN.matcher(name); if (!matcher.matches()) { return null; @@ -448,7 +481,8 @@ public class GnuDemanglerParser { String fullText = matcher.group(1); String params = matcher.group(2); String trailing = matcher.group(3); - return new LambdaName(fullText, params, trailing); + String modifiers = matcher.group(4); + return new LambdaName(fullText, params, trailing, modifiers); } private String stripOffTemplates(String string) { @@ -587,7 +621,18 @@ public class GnuDemanglerParser { } } - i = getFunctionPointerCloseParen(parameterString, i); + // + // we wish to move past two sets of parens for function pointers; however, sometimes + // we have code with only one set of parens; for example: + // unsigned long (*)(long const &) + // or + // iterator + // + int end = findBalancedEnd(parameterString, i, '(', ')'); + if (end == -1) { + end = parameterString.length(); + } + i = end; } } if (startIndex < parameterString.length()) { @@ -597,40 +642,6 @@ public class GnuDemanglerParser { return parameters; } - private int getFunctionPointerCloseParen(String parameterString, int currentIndex) { - int firstCloseParen = parameterString.indexOf(')', currentIndex); - if (firstCloseParen == -1) { - throw new DemanglerParseException( - "Unable to find closing paren for parameter string: " + parameterString); - } - - // - // we wish to move past two sets of parens for function pointers; however, sometimes - // we have code with only one set of parens; for example: - // unsigned long (*)(long const &) - // or - // iterator - // - boolean foundNextStart = false; - int length = parameterString.length(); - for (int i = currentIndex; i < length; i++) { - char ch = parameterString.charAt(i); - if (ch == ')') { - return i; - } - else if (ch == '(') { - foundNextStart = true; - } - else if (ch == ',') { - if (!foundNextStart) { - return firstCloseParen;// no new set of parens found - } - } - } - - return firstCloseParen; - } - /** * This method converts each parameter string into * actual DemangledDataType objects. @@ -639,25 +650,47 @@ public class GnuDemanglerParser { List parameters = new ArrayList<>(); for (String parameter : parameterStrings) { - DemangledDataType ddt = parseDataType(parameter); - parameters.add(ddt); + DemangledDataType dt = parseParameter(parameter); + parameters.add(dt); } return parameters; } - private DemangledDataType parseDataType(String fullDatatype) { + private DemangledDataType parseParameter(String parameter) { - Matcher castMatcher = CAST_PATTERN.matcher(fullDatatype); + Matcher castMatcher = CAST_PATTERN.matcher(parameter); if (castMatcher.matches()) { // special case: template parameter with a cast (just make the datatype // be the name of the template parameter, since it will just be a display // attribute for the templated type) - return new DemangledDataType(mangledSource, demangledSource, fullDatatype); + return new DemangledDataType(mangledSource, demangledSource, parameter); + } + + Matcher matcher = VARARGS_IN_PARENS.matcher(parameter); + if (matcher.matches()) { + String inside = matcher.group(1); + DemangledDataType dt = parseDataType(inside); + dt.setVarArgs(); + return dt; + + } + return parseDataType(parameter); + } + + private DemangledDataType parseReturnType(String returnType) { + return parseDataType(returnType); + } + + private DemangledDataType parseDataType(String fullDatatype) { + + DemangledDataType dt = createTypeInNamespace(fullDatatype); + String datatype = dt.getDemangledName(); + + if ("*".equals(datatype)) { + return createMemberPointer(fullDatatype); } - DemangledDataType ddt = createTypeInNamespace(fullDatatype); - String datatype = ddt.getDemangledName(); boolean finishedName = false; for (int i = 0; i < datatype.length(); ++i) { char ch = datatype.charAt(i); @@ -670,11 +703,11 @@ public class GnuDemanglerParser { finishedName = true; if (VAR_ARGS.equals(datatype)) { - ddt.setVarArgs(); + dt.setVarArgs(); } else { String name = datatype.substring(0, i).trim(); - ddt.setName(name); + dt.setName(name); } } @@ -690,7 +723,7 @@ public class GnuDemanglerParser { String templateContent = datatype.substring(contentStart, templateEnd); DemangledTemplate template = parseTemplate(templateContent); - ddt.setTemplate(template); + dt.setTemplate(template); i = templateEnd; } else if (ch == '(') {// start of function pointer or array ref/pointer @@ -699,23 +732,35 @@ public class GnuDemanglerParser { // e.g., unsigned long (*)(long const &) // array pointer/refs // e.g., short (&)[7] - // + // lambda function + // e.g., {lambda(NS1::Class1 const&, int, int)#1} const& + // {lambda(auto:1&&)#1}>&& + // + + LambdaName lambdaName = getLambdaName(datatype); // check for array case Matcher arrayMatcher = ARRAY_POINTER_REFERENCE_PATTERN.matcher(datatype); if (arrayMatcher.matches()) { - Demangled namespace = ddt.getNamespace(); + Demangled namespace = dt.getNamespace(); String name = arrayMatcher.group(1);// group 0 is the entire string - ddt = parseArrayPointerOrReference(datatype, name, arrayMatcher); - ddt.setNamespace(namespace); + dt = parseArrayPointerOrReference(datatype, name, arrayMatcher); + dt.setNamespace(namespace); i = arrayMatcher.end(); } + 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 + } else { - int startParenCount = - StringUtilities.countOccurrences(datatype.substring(i), '('); - boolean hasPointerParens = startParenCount == 2; + // e.g., unsigned long (*)(long const &) + boolean hasPointerParens = hasConsecutiveSetsOfParens(datatype.substring(i)); if (hasPointerParens) { - Demangled namespace = ddt.getNamespace(); + Demangled namespace = dt.getNamespace(); DemangledFunctionPointer dfp = parseFunctionPointer(datatype); int firstParenEnd = datatype.indexOf(')', i + 1); int secondParenEnd = datatype.indexOf(')', firstParenEnd + 1); @@ -725,13 +770,13 @@ public class GnuDemanglerParser { } dfp.getReturnType().setNamespace(namespace); - ddt = dfp; + dt = dfp; i = secondParenEnd + 1; // two sets of parens (normal case) } else { // parse as a function pointer, but display as a function - Demangled namespace = ddt.getNamespace(); + Demangled namespace = dt.getNamespace(); DemangledFunctionPointer dfp = parseFunction(datatype, i); int firstParenEnd = datatype.indexOf(')', i + 1); if (firstParenEnd == -1) { @@ -740,26 +785,26 @@ public class GnuDemanglerParser { } dfp.getReturnType().setNamespace(namespace); - ddt = dfp; + dt = dfp; i = firstParenEnd + 1;// two sets of parens (normal case) } } } else if (ch == '*') { - ddt.incrementPointerLevels(); + dt.incrementPointerLevels(); continue; } else if (ch == '&') { - if (!ddt.isReference()) { - ddt.setReference(); + if (!dt.isReference()) { + dt.setReference(); } else { - ddt.incrementPointerLevels(); + dt.setRValueReference(); } continue; } else if (ch == '[') { - ddt.setArray(ddt.getArrayDimensions() + 1); + dt.setArray(dt.getArrayDimensions() + 1); i = datatype.indexOf(']', i + 1); continue; } @@ -767,53 +812,87 @@ public class GnuDemanglerParser { String substr = datatype.substring(i); if (substr.startsWith("const")) { - ddt.setConst(); + dt.setConst(); i += 4; } else if (substr.startsWith("struct")) { - ddt.setStruct(); + dt.setStruct(); i += 5; } else if (substr.startsWith("class")) { - ddt.setClass(); + dt.setClass(); i += 4; } else if (substr.startsWith("enum")) { - ddt.setEnum(); + dt.setEnum(); i += 3; } - else if (ddt.getName().equals("long")) { + else if (dt.getName().equals("long")) { if (substr.startsWith("long")) { - ddt.setName(DemangledDataType.LONG_LONG); + dt.setName(DemangledDataType.LONG_LONG); i += 3; } else if (substr.startsWith("double")) { - ddt.setName(DemangledDataType.LONG_DOUBLE); + dt.setName(DemangledDataType.LONG_DOUBLE); i += 5; } } // unsigned can also mean unsigned long, int - else if (ddt.getName().equals("unsigned")) { - ddt.setUnsigned(); + else if (dt.getName().equals("unsigned")) { + dt.setUnsigned(); if (substr.startsWith("long")) { - ddt.setName(DemangledDataType.LONG); + dt.setName(DemangledDataType.LONG); i += 3; } else if (substr.startsWith("int")) { - ddt.setName(DemangledDataType.INT); + dt.setName(DemangledDataType.INT); i += 2; } else if (substr.startsWith("short")) { - ddt.setName(DemangledDataType.SHORT); + dt.setName(DemangledDataType.SHORT); i += 4; } else if (substr.startsWith("char")) { - ddt.setName(DemangledDataType.CHAR); + dt.setName(DemangledDataType.CHAR); i += 3; } } } - return ddt; + return dt; + } + + private boolean hasConsecutiveSetsOfParens(String text) { + int end = findBalancedEnd(text, 0, '(', ')'); + if (end < -1) { + return false; + } + + String remaining = text.substring(end + 1).trim(); + return remaining.startsWith("("); + } + + 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); + int space = typeWithoutPointer.indexOf(' '); + DemangledDataType dt; + if (space != -1) { + String type = typeWithoutPointer.substring(0, space); + dt = createTypeInNamespace(type); + + String parentType = typeWithoutPointer.substring(space + 1); + DemangledDataType parentDt = createTypeInNamespace(parentType); + dt.setNamespace(parentDt); + } + else { + dt = createTypeInNamespace(typeWithoutPointer); + } + + dt.incrementPointerLevels(); + return dt; } private boolean isDataTypeNameCharacter(char ch) { @@ -837,26 +916,31 @@ public class GnuDemanglerParser { } /** - * Scans the given string from the given offset looking for a template and reporting the - * index of the closing template character '>' or -1 if no templates are found + * Scans the given string from the given offset looking for a balanced {@code close} + * character. This algorithm will not report a match for the end character until the + * {@code open} character has first been found. This allows clients to scan from anywhere + * in a string to find an open and start character combination, including at or before the + * desired opening character. * * @param string the input string * @param start the start position within the string - * @return the template end index; -1 if no templates found + * @param open the open character (e.g, '(' or '<') + * @param close the close character (e.g, ')' or '>') + * @return the end index; -1 if no templates found */ - private int findTemplateEnd(String string, int start) { + private int findBalancedEnd(String string, int start, char open, char close) { boolean found = false; int depth = 0; for (int i = start; i < string.length(); i++) { - switch (string.charAt(i)) { - case '<': - depth++; - found = true; - break; - case '>': - depth--; - break; + + char c = string.charAt(i); + if (c == open) { + depth++; + found = true; + } + else if (c == close) { + depth--; } if (found && depth == 0) { @@ -867,23 +951,119 @@ public class GnuDemanglerParser { return -1; } - // assumption: the given index is in a template - // Walk backwards to find the template start - private int findMatchingTemplateStart(String string, int templateEnd) { + /** + * Scans the given string from the given offset looking for a balanced {@code open} + * character. This algorithm will not report a match for the open character until the + * {@code end} character has first been found. This allows clients to scan from anywhere + * in a string to find an open and start character combination, including at or before the + * desired opening character. + * + * @param string the input string + * @param start the start position within the string + * @param open the open character (e.g, '(' or '<') + * @param close the close character (e.g, ')' or '>') + * @return the end index; -1 if no templates found + */ + private int findBalancedStart(String string, int start, char open, char close) { - int depth = 1; - for (int i = templateEnd - 1; i >= 0; i--) { - switch (string.charAt(i)) { - case '<': - depth--; - break; - case '>': - depth++; - break; + boolean found = false; + int depth = 0; + for (int i = start; i >= 0; i--) { + + char c = string.charAt(i); + if (c == open) { + depth--; + } + else if (c == close) { + depth++; + found = true; } - if (depth == 0) { - return i;// found our opening tag + if (found && depth == 0) { + return i; + } + } + + return -1; + } + + private int findTemplateEnd(String string, int start) { + return findBalancedEnd(string, start, '<', '>'); + } + + private int findTemplateStart(String string, int templateEnd) { + return findBalancedStart(string, templateEnd, '<', '>'); + } + + /** + * Walks backward from the given start position to find the next namespace separator. This + * allows clients to determine if a given position is inside of a namespace. + * + * @param text the text to search + * @param start the start position + * @param stop the stop position + * @return the start index of the namespace entry containing the current {@code start} + * index; -1 if no namespace start is found + */ + private int findNamespaceStart(String text, int start, int stop) { + + if (!text.contains(Namespace.DELIMITER)) { + return -1; + } + + int colonCount = 0; + int parenDepth = 0; + int templateDepth = 0; + int braceDepth = 0; + boolean isNested = false; + + for (int i = start; i >= stop; i--) { + + char c = text.charAt(i); + switch (c) { + case ':': { + colonCount++; + if (colonCount == 2) { + if (!isNested) { + return i + 2; + } + colonCount = 0; + } + break; + } + case ' ': { + if (!isNested) { + return -1; // a space implies a return type when not nested + } + break; + } + case '(': { + isNested = --parenDepth > 0 || templateDepth > 0 || braceDepth > 0; + break; + } + case ')': { + isNested = ++parenDepth > 0 || templateDepth > 0 || braceDepth > 0; + break; + } + case '<': { + isNested = parenDepth > 0 || --templateDepth > 0 || braceDepth > 0; + break; + } + case '>': { + isNested = parenDepth > 0 || ++templateDepth > 0 || braceDepth > 0; + break; + } + case '{': { + isNested = parenDepth > 0 || templateDepth > 0 || --braceDepth > 0; + break; + } + case '}': { + isNested = parenDepth > 0 || templateDepth > 0 || ++braceDepth > 0; + break; + } + + default: + continue; } } @@ -891,25 +1071,21 @@ public class GnuDemanglerParser { } private DemangledDataType createTypeInNamespace(String name) { - SymbolPath path = new SymbolPath(name); - List names = path.asList(); - + List names = SymbolPathParser.parse(name, false); DemangledType namespace = null; if (names.size() > 1) { namespace = convertToNamespaces(names.subList(0, names.size() - 1)); } String datatypeName = names.get(names.size() - 1); - DemangledDataType ddt = new DemangledDataType(mangledSource, demangledSource, datatypeName); - ddt.setName(datatypeName); - ddt.setNamespace(namespace); - return ddt; + DemangledDataType dt = new DemangledDataType(mangledSource, demangledSource, datatypeName); + dt.setName(datatypeName); + dt.setNamespace(namespace); + return dt; } private void setNameAndNamespace(DemangledObject object, String name) { - SymbolPath path = new SymbolPath(name); - List names = path.asList(); - + List names = SymbolPathParser.parse(name, false); DemangledType namespace = null; if (names.size() > 1) { namespace = convertToNamespaces(names.subList(0, names.size() - 1)); @@ -923,8 +1099,7 @@ public class GnuDemanglerParser { private void setNamespace(DemangledObject object, String name) { - SymbolPath path = new SymbolPath(name); - List names = path.asList(); + List names = SymbolPathParser.parse(name, false); object.setNamespace(convertToNamespaces(names)); } @@ -948,13 +1123,13 @@ public class GnuDemanglerParser { // int (*)[8] // char (&)[7] - DemangledDataType ddt = new DemangledDataType(mangledSource, demangledSource, name); + DemangledDataType dt = new DemangledDataType(mangledSource, demangledSource, name); String type = matcher.group(2); if (type.equals("*")) { - ddt.incrementPointerLevels(); + dt.incrementPointerLevels(); } else if (type.equals("&")) { - ddt.setReference(); + dt.setReference(); } else { throw new DemanglerParseException("Unexpected charater inside of parens: " + type); @@ -962,9 +1137,9 @@ public class GnuDemanglerParser { String arraySubscripts = matcher.group(3); int n = StringUtilities.countOccurrences(arraySubscripts, '['); - ddt.setArray(n); + dt.setArray(n); - return ddt; + return dt; } private DemangledFunctionPointer parseFunctionPointer(String functionString) { @@ -985,7 +1160,8 @@ public class GnuDemanglerParser { //unsigned long (long const &) int parenStart = functionString.indexOf('(', offset); - int parenEnd = functionString.indexOf(')', parenStart + 1); + int parenEnd = findBalancedEnd(functionString, parenStart, '(', ')'); + //int parenEnd = functionString.indexOf(')', parenStart + 1); String returnType = functionString.substring(0, parenStart).trim(); @@ -1003,7 +1179,8 @@ public class GnuDemanglerParser { List parameters = parseParameters(paramerterString); DemangledFunctionPointer dfp = new DemangledFunctionPointer(mangledSource, demangledSource); - dfp.setReturnType(parseDataType(returnType)); + DemangledDataType returnDataType = parseReturnType(returnType); + dfp.setReturnType(returnDataType); for (DemangledDataType parameter : parameters) { dfp.addParameter(parameter); } @@ -1231,7 +1408,17 @@ public class GnuDemanglerParser { @Override boolean matches(String text) { matcher = OVERLOAD_OPERATOR_NAME_PATTERN.matcher(text); - return matcher.matches(); + if (!matcher.matches()) { + return false; + } + + int operatorStart = matcher.start(2); + int leafStart = findNamespaceStart(demangled, text.length() - 1, operatorStart); + if (leafStart > operatorStart) { + return false; // operator is inside of a non-leaf namespace entry + } + + return true; } @Override @@ -1248,14 +1435,8 @@ public class GnuDemanglerParser { // NS1::operator<(NS1::Coordinate const &,NS1::Coordinate const &) // String operatorChars = matcher.group(2); - String templates = matcher.group(3); int start = matcher.start(2); // operator chars start - int end = matcher.end(3); // templates end - - if (templates == null) { - templates = ""; - end = matcher.end(2); // no templates; end of the operator chars - } + int end = matcher.end(2); // operator chars start // // The 'operator' functions have symbols that confuse our default function parsing. @@ -1263,11 +1444,16 @@ public class GnuDemanglerParser { // template parsing to fail. To defeat the failure, we will install a temporary // function name here and then restore it after parsing is finished. // - String rawPrefix = OPERATOR + demangled.substring(start, end); - String placeholder = "TEMPNAMEPLACEHOLDERVALUE"; - String tempName = demangled.replace(rawPrefix, placeholder); - DemangledFunction function = (DemangledFunction) parseFunctionOrVariable(tempName); + String templates = getTemplates(end); + end = end + templates.length(); + + // a string to replace operator chars; this value will be overwritten the name is set + String placeholder = "TEMPNAMEPLACEHOLDERVALUE"; + String baseOperator = OPERATOR + demangled.substring(start, end); + String fixedFunction = demangled.replace(baseOperator, placeholder); + + DemangledFunction function = (DemangledFunction) parseFunctionOrVariable(fixedFunction); function.setOverloadedOperator(true); String simpleName = OPERATOR + operatorChars; @@ -1283,6 +1469,30 @@ public class GnuDemanglerParser { return function; } + + private String getTemplates(int start) { + String templates = ""; + boolean hasTemplates = nextCharIs(demangled, start, '<'); + if (hasTemplates) { + int templateStart = start; + int templateEnd = findTemplateEnd(demangled, templateStart); + if (templateEnd == -1) { + // should not happen + Msg.debug(this, "Unable to find template end for operator: " + demangled); + return templates; + } + templates = demangled.substring(templateStart, templateEnd + 1); + } + return templates; + } + } + + private boolean nextCharIs(String text, int index, char c) { + char next = text.charAt(index); + while (next == ' ') { + next = text.charAt(++index); + } + return next == c; } private class ConversionOperatorHandler extends OperatorHandler { @@ -1315,7 +1525,7 @@ public class GnuDemanglerParser { DemangledFunction method = new DemangledFunction(mangledSource, demangledSource, (String) null); - DemangledDataType returnType = parseDataType(fullReturnType); + DemangledDataType returnType = parseReturnType(fullReturnType); if (isConst) { returnType.setConst(); } @@ -1331,8 +1541,8 @@ public class GnuDemanglerParser { // shortReturnType: string String templatelessReturnType = stripOffTemplates(fullReturnType); - SymbolPath path = new SymbolPath(templatelessReturnType); - String shortReturnTypeName = path.getName(); + List path = SymbolPathParser.parse(templatelessReturnType, false); + String shortReturnTypeName = path.get(path.size() - 1); // // The preferred name: 'operator basic_string()' @@ -1427,7 +1637,7 @@ public class GnuDemanglerParser { int templateEnd = findTemplateEnd(text, 0); int templateStart = -1; if (templateEnd != -1) { - templateStart = findMatchingTemplateStart(text, templateEnd); + templateStart = findTemplateStart(text, templateEnd); } if (paramStart > templateStart && paramStart < templateEnd) { // ignore parentheses inside of templates (they are cast operators) @@ -1481,16 +1691,19 @@ public class GnuDemanglerParser { } } + // {lambda(void const*, unsigned int)#1} private class LambdaName { private String fullText; private String params; + private String id; private String trailing; - LambdaName(String fullText, String params, String trailing) { + LambdaName(String fullText, String params, String id, String trailing) { this.fullText = fullText; this.params = params; - this.trailing = trailing; + this.id = id; + this.trailing = trailing == null ? "" : trailing; } String getFullText() { @@ -1502,6 +1715,7 @@ public class GnuDemanglerParser { ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.JSON_STYLE); return builder.append("fullText", fullText) .append("params", params) + .append("id", id) .append("trailing", trailing) .toString(); } 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 af908cb4fc..50b908baff 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 @@ -842,6 +842,32 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { assertEquals("Magick::Coordinate const &", parameters.get(1).getSignature()); } + @Test + public void testOperatorAsNamespace() throws Exception { + + // + // Mangled: __ZN3WTF15__visitor_tableINS_7VisitorIZZN7WebCore12SubtleCrypto11generateKeyERN3JSC9ExecStateEONS_7VariantIJNS4_6StrongINS4_8JSObjectEEENS_6StringEEEEbONS_6VectorINS2_14CryptoKeyUsageELm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEEONS_3RefINS2_15DeferredPromiseENS_13DumbPtrTraitsISL_EEEEEN4$_10clEONS7_IJNS_6RefPtrINS2_9CryptoKeyENSM_ISS_EEEENS2_13CryptoKeyPairEEEEEUlRSU_E_JZZNS3_11generateKeyES6_SD_bSJ_SP_ENSQ_clESX_EUlRSV_E_EEEJSU_SV_EE12__trampolineE + // + // Demangled: WTF::__visitor_table, WTF::String>&&, bool, WTF::Vector&&, WTF::Ref >&&)::$_10::operator()(WTF::Variant >, WebCore::CryptoKeyPair>&&)::{lambda(WTF::RefPtr >&)#1}, WebCore::SubtleCrypto::generateKey(JSC::ExecState&, WTF::Variant, WTF::String>&&, bool, WTF::Vector&&, WTF::Ref >&&)::$_10::operator()(WTF::Variant >, WebCore::CryptoKeyPair>&&)::{lambda(WebCore::CryptoKeyPair&)#1}>, WTF::RefPtr >, WebCore::CryptoKeyPair>::__trampoline + // + + DemangledObject object = parser.parse( + "__ZN3WTF15__visitor_tableINS_7VisitorIZZN7WebCore12SubtleCrypto11generateKeyERN3JSC9ExecStateEONS_7VariantIJNS4_6StrongINS4_8JSObjectEEENS_6StringEEEEbONS_6VectorINS2_14CryptoKeyUsageELm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEEONS_3RefINS2_15DeferredPromiseENS_13DumbPtrTraitsISL_EEEEEN4$_10clEONS7_IJNS_6RefPtrINS2_9CryptoKeyENSM_ISS_EEEENS2_13CryptoKeyPairEEEEEUlRSU_E_JZZNS3_11generateKeyES6_SD_bSJ_SP_ENSQ_clESX_EUlRSV_E_EEEJSU_SV_EE12__trampolineE", + "WTF::__visitor_table, WTF::String>&&, bool, WTF::Vector&&, WTF::Ref >&&)::$_10::operator()(WTF::Variant >, WebCore::CryptoKeyPair>&&)::{lambda(WTF::RefPtr >&)#1}, WebCore::SubtleCrypto::generateKey(JSC::ExecState&, WTF::Variant, WTF::String>&&, bool, WTF::Vector&&, WTF::Ref >&&)::$_10::operator()(WTF::Variant >, WebCore::CryptoKeyPair>&&)::{lambda(WebCore::CryptoKeyPair&)#1}>, WTF::RefPtr >, WebCore::CryptoKeyPair>::__trampoline"); + + assertNotNull(object); + assertType(object, DemangledVariable.class); + + String name = "__trampoline"; + assertName(object, name, "WTF", + "__visitor_table,WTF::String>&&,bool,WTF::Vector&&,WTF::Ref>&&)::$_10::operator()(WTF::Variant>,WebCore::CryptoKeyPair>&&)::{lambda(WTF::RefPtr>&)#1},WebCore::SubtleCrypto::generateKey(JSC::ExecState&,WTF::Variant,WTF::String>&&,bool,WTF::Vector&&,WTF::Ref>&&)::$_10::operator()(WTF::Variant>,WebCore::CryptoKeyPair>&&)::{lambda(WebCore::CryptoKeyPair&)#1}>,WTF::RefPtr>,WebCore::CryptoKeyPair>"); + + String signature = object.getSignature(false); + assertEquals( + "WTF::__visitor_table,WTF::String>&&,bool,WTF::Vector&&,WTF::Ref>&&)::$_10::operator()(WTF::Variant>,WebCore::CryptoKeyPair>&&)::{lambda(WTF::RefPtr>&)#1},WebCore::SubtleCrypto::generateKey(JSC::ExecState&,WTF::Variant,WTF::String>&&,bool,WTF::Vector&&,WTF::Ref>&&)::$_10::operator()(WTF::Variant>,WebCore::CryptoKeyPair>&&)::{lambda(WebCore::CryptoKeyPair&)#1}>,WTF::RefPtr>,WebCore::CryptoKeyPair>::__trampoline", + signature); + } + @Test public void testOverloadedShiftOperatorTemplated_RightShift() { parser = new GnuDemanglerParser(); @@ -1166,6 +1192,25 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { object.getSignature(false)); } + @Test + public void testAnonymousNamespaceInParameter_FirstNamespace() throws Exception { + // + // Mangled: __ZL7pperrorPN12_GLOBAL__N_17ContextEPKc + // + // Demangled: pperror((anonymous namespace)::Context*, char const*) + // + String mangled = "_ZL7pperrorPN12_GLOBAL__N_17ContextEPKc"; + + String demangled = process.demangle(mangled); + + DemangledObject object = parser.parse(mangled, demangled); + assertType(object, DemangledFunction.class); + assertName(object, "pperror"); + + assertEquals("undefined pperror((anonymous_namespace)::Context *,char const *)", + object.getSignature(false)); + } + @Test public void testTemplatedParametersWithCast() throws Exception { // @@ -1233,7 +1278,7 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { parameters.get(0).getTemplate().toString()); assertEquals( - "undefined Core::AsyncFile::perform(WTF::F (Core::File &)> * &)", + "undefined Core::AsyncFile::perform(WTF::F (Core::File &)> &&)", object.getSignature(false)); } @@ -1327,6 +1372,35 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { object.getSignature(false)); } + @Test + public void testTemplatesThatContainFunctionSignatures_Regression() throws Exception { + + // + // Mangled: _ZN7WebCore27ContentFilterUnblockHandlerC2EN3WTF6StringENSt3__18functionIFvNS4_IFvbEEEEEE + // + // Demangled: WebCore::ContentFilterUnblockHandler::ContentFilterUnblockHandler(WTF::String, std::__1::function)>) + // + // Note: this parameter name caused an infinite loop + // + // function)>) + // + + DemangledObject object = parser.parse( + "_ZN7WebCore27ContentFilterUnblockHandlerC2EN3WTF6StringENSt3__18functionIFvNS4_IFvbEEEEEE", + "WebCore::ContentFilterUnblockHandler::ContentFilterUnblockHandler(WTF::String, std::__1::function)>)"); + + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String name = "ContentFilterUnblockHandler"; + assertName(object, name, "WebCore", "ContentFilterUnblockHandler"); + + String signature = object.getSignature(false); + assertEquals( + "undefined WebCore::ContentFilterUnblockHandler::ContentFilterUnblockHandler(WTF::String,std::__1::function)>)", + signature); + } + @Test public void testVtableParsingError_NoSpaceBeforeTrailingDigits() throws Exception { // @@ -1460,6 +1534,208 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { signature); } + @Test + public void testNamespaceElementWithMultipleFunctionParentheses() throws Exception { + + // __ZN7brigand13for_each_argsIZN7WebCore11JSConverterINS1_8IDLUnionIJNS1_7IDLNullENS1_12IDLDOMStringENS1_21IDLUnrestrictedDoubleEEEEE7convertERN3JSC9ExecStateERNS1_17JSDOMGlobalObjectERKN3WTF7VariantIJDnNSE_6StringEdEEEEUlOT_E_JNS_5type_INSt3__117integral_constantIlLl0EEEEENSN_INSP_IlLl1EEEEENSN_INSP_IlLl2EEEEEEEESK_SK_DpOT0_ + + DemangledObject object = parser.parse( + "__ZN7brigand13for_each_argsIZN7WebCore11JSConverterINS1_8IDLUnionIJNS1_7IDLNullENS1_12IDLDOMStringENS1_21IDLUnrestrictedDoubleEEEEE7convertERN3JSC9ExecStateERNS1_17JSDOMGlobalObjectERKN3WTF7VariantIJDnNSE_6StringEdEEEEUlOT_E_JNS_5type_INSt3__117integral_constantIlLl0EEEEENSN_INSP_IlLl1EEEEENSN_INSP_IlLl2EEEEEEEESK_SK_DpOT0_", + "WebCore::JSConverter >::convert(JSC::ExecState&, WebCore::JSDOMGlobalObject&, WTF::Variant const&)::{lambda(auto:1&&)#1} brigand::for_each_args >::convert(JSC::ExecState&, WebCore::JSDOMGlobalObject&, WTF::Variant const&)::{lambda(auto:1&&)#1}, brigand::type_ >, WebCore::JSConverter >::convert(JSC::ExecState&, WebCore::JSDOMGlobalObject&, WTF::Variant const&)::{lambda(auto:1&&)#1} >, WebCore::JSConverter >::convert(JSC::ExecState&, WebCore::JSDOMGlobalObject&, WTF::Variant const&)::{lambda(auto:1&&)#1} > >(WebCore::JSConverter >::convert(JSC::ExecState&, WebCore::JSDOMGlobalObject&, WTF::Variant const&)::{lambda(auto:1&&)#1}, brigand::type_ >&&, WebCore::JSConverter >::convert(JSC::ExecState&, WebCore::JSDOMGlobalObject&, WTF::Variant const&)::{lambda(auto:1&&)#1} >&&, WebCore::JSConverter >::convert(JSC::ExecState&, WebCore::JSDOMGlobalObject&, WTF::Variant const&)::{lambda(auto:1&&)#1} >&&)"); + + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String name = + "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}>>"; + assertName(object, name, + "brigand"); + + 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}> &&)", + signature); + } + + @Test + public void testFunctionParameterWithMemberPointer() throws Exception { + + // + // Mangled: __ZN7WebCore22FontSelectionAlgorithm16filterCapabilityEPbMS0_KFNS0_14DistanceResultENS_25FontSelectionCapabilitiesEEMS3_NS_18FontSelectionRangeE + // + // Demangled: WebCore::FontSelectionAlgorithm::filterCapability(bool*, WebCore::FontSelectionAlgorithm::DistanceResult (WebCore::FontSelectionAlgorithm::*)(WebCore::FontSelectionCapabilities) const, WebCore::FontSelectionRange WebCore::FontSelectionCapabilities::*) + // + // + // WebCore::FontSelectionAlgorithm::filterCapability + // ( + // bool*, + // WebCore::FontSelectionAlgorithm::DistanceResult (WebCore::FontSelectionAlgorithm::*)(WebCore::FontSelectionCapabilities) const, + // WebCore::FontSelectionRange WebCore::FontSelectionCapabilities::* + // ) + // + // This demangled string introduces a new construct: + // + // FontSelectionRange FontSelectionCapabilities::* + // + // where the type is 'FontSelectionRange' which is a member of 'FontSelectionCapabilities' + // + DemangledObject object = parser.parse( + "__ZN7WebCore22FontSelectionAlgorithm16filterCapabilityEPbMS0_KFNS0_14DistanceResultENS_25FontSelectionCapabilitiesEEMS3_NS_18FontSelectionRangeE", + "WebCore::FontSelectionAlgorithm::filterCapability(bool*, WebCore::FontSelectionAlgorithm::DistanceResult (WebCore::FontSelectionAlgorithm::*)(WebCore::FontSelectionCapabilities) const, WebCore::FontSelectionRange WebCore::FontSelectionCapabilities::*)"); + + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String name = "filterCapability"; + assertName(object, name, "WebCore", "FontSelectionAlgorithm"); + + String signature = object.getSignature(false); + assertEquals( + "undefined WebCore::FontSelectionAlgorithm::filterCapability(bool *,WebCore::FontSelectionAlgorithm::DistanceResult ()(WebCore::FontSelectionCapabilities) const,WebCore::FontSelectionCapabilities::FontSelectionRange *)", + signature); + } + + @Test + public void testFunctionParameterWithMultipleParentheses() throws Exception { + + // + // Mangled: __ZN7WebCore12TextCodecICU14registerCodecsEPFvPKcON3WTF8FunctionIFNSt3__110unique_ptrINS_9TextCodecENS5_14default_deleteIS7_EEEEvEEEE + // + // Demangled: undefined WebCore::TextCodecICU::registerCodecs(void ()(char const *,WTF::Function> ()> &&)) + // + // The regression tested here revolves around this parameter: + // + // void ()(char const *,WTF::Function> ()> && + // + // (note the trailing '()' chars) + // + + DemangledObject object = parser.parse( + "__ZN7WebCore12TextCodecICU14registerCodecsEPFvPKcON3WTF8FunctionIFNSt3__110unique_ptrINS_9TextCodecENS5_14default_deleteIS7_EEEEvEEEE", + "undefined WebCore::TextCodecICU::registerCodecs(void ()(char const *,WTF::Function> ()> &&))"); + + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String name = "registerCodecs"; + assertName(object, name, "WebCore", "TextCodecICU"); + + String signature = object.getSignature(false); + assertEquals( + "undefined WebCore::TextCodecICU::registerCodecs(void ()(char const *,WTF::Function> ()> &&))", + signature); + } + + @Test + public void testFunctionWithVarargsRvalueParameter() throws Exception { + + // __ZN3WTF15__visit_helper2ILl1ELm1EJEE7__visitINS_7VisitorIZNKS_17TextBreakIterator9followingEjEUlRKT_E_JEEEJRKNS_7VariantIJNS_20TextBreakIteratorICUENS_19TextBreakIteratorCFEEEEEEENS_27__multi_visitor_return_typeIS5_JDpT0_EE6__typeERS5_DpOSH_ + // + // + // this demangled string introduces a new construct: + // + // (WTF::__multi_visitor_return_type&&)... + // + // where the above is a parameter to function, where the params look like: + // ( + // WTF::Visitor&, + // (WTF::__multi_visitor_return_type&&)... + // ) + // + + DemangledObject object = parser.parse( + "__ZN3WTF15__visit_helper2ILl1ELm1EJEE7__visitINS_7VisitorIZNKS_17TextBreakIterator9followingEjEUlRKT_E_JEEEJRKNS_7VariantIJNS_20TextBreakIteratorICUENS_19TextBreakIteratorCFEEEEEEENS_27__multi_visitor_return_typeIS5_JDpT0_EE6__typeERS5_DpOSH_", + "WTF::__multi_visitor_return_type, WTF::Variant const&>::__type WTF::__visit_helper2<1l, 1ul>::__visit, WTF::Variant const&>(WTF::Visitor&, (WTF::__multi_visitor_return_type&&)...)"); + + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String name = + "__visit,WTF::Variantconst&>"; + assertName(object, name, "WTF", "__visit_helper2<1l,1ul>"); + + String signature = object.getSignature(false); + assertEquals( + "WTF::__multi_visitor_return_type,WTF::Variantconst&>::__type WTF::__visit_helper2<1l,1ul>::__visit,WTF::Variantconst&>(WTF::Visitor &,WTF::__multi_visitor_return_type &&)", + signature); + } + + @Test + public void testOperator_Equals_ExcessivelyTemplated() throws Exception { + + DemangledObject object = parser.parse( + "__ZN3WTF8FunctionIFvvEEaSIZN7WebCore9IDBClient24TransactionOperationImplIJRKNS4_18IDBObjectStoreInfoEEEC1ERNS4_14IDBTransactionEMSB_FvRKNS4_13IDBResultDataEEMSB_FvRNS5_20TransactionOperationES9_ES9_EUlvE_vEERS2_OT_", + "WTF::Function& WTF::Function::operator=::TransactionOperationImpl(WebCore::IDBTransaction&, void (WebCore::IDBTransaction::*)(WebCore::IDBResultData const&), void (WebCore::IDBTransaction::*)(WebCore::IDBClient::TransactionOperation&, WebCore::IDBObjectStoreInfo const&), WebCore::IDBObjectStoreInfo const&)::{lambda()#1}, void>(WebCore::IDBClient::TransactionOperationImpl::TransactionOperationImpl(WebCore::IDBTransaction&, void (WebCore::IDBTransaction::*)(WebCore::IDBResultData const&), void (WebCore::IDBTransaction::*)(WebCore::IDBClient::TransactionOperation&, WebCore::IDBObjectStoreInfo const&), WebCore::IDBObjectStoreInfo const&)::{lambda()#1}&&)"); + + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String name = + "operator="; + assertName(object, name, "WTF", "Function"); + + String signature = object.getSignature(false); + assertEquals( + "WTF::Function & WTF::Function::operator=::TransactionOperationImpl(WebCore::IDBTransaction&,void(WebCore::IDBTransaction::*)(WebCore::IDBResultData_const&),void(WebCore::IDBTransaction::*)(WebCore::IDBClient::TransactionOperation&,WebCore::IDBObjectStoreInfo_const&),WebCore::IDBObjectStoreInfo_const&)::{lambda()#1},void>(WebCore::IDBClient::TransactionOperationImpl::TransactionOperationImpl(WebCore::IDBTransaction&,void(WebCore::IDBTransaction::*)(WebCore::IDBResultData_const&),void(WebCore::IDBTransaction::*)(WebCore::IDBClient::TransactionOperation&,WebCore::IDBObjectStoreInfo_const&),WebCore::IDBObjectStoreInfo_const&)::{lambda()#1} &&)", + signature); + + } + + @Test + public void testLambdaWithTemplates() throws Exception { + + // {lambda(auto:1&&)#1}>&& + DemangledObject object = parser.parse( + "_ZN3WTF6VectorINS_9RetainPtrI29AVAssetResourceLoadingRequestEELm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEE17removeAllMatchingIZNS6_9removeAllIPS2_EEjRKT_EUlRKS3_E_EEjSC_m", + "WebCore::Converter, WebCore::IDLInterface > >::convert(JSC::ExecState&, JSC::JSValue)::{lambda(auto:1&&)#1} brigand::for_each_args, WebCore::IDLInterface > >::convert(JSC::ExecState&, JSC::JSValue)::{lambda(auto:1&&)#1}, brigand::type_ >, WebCore::Converter, WebCore::IDLInterface > >::convert(JSC::ExecState&, JSC::JSValue)::{lambda(auto:1&&)#1} > >(WebCore::Converter, WebCore::IDLInterface > >::convert(JSC::ExecState&, JSC::JSValue)::{lambda(auto:1&&)#1}, brigand::type_ >&&, WebCore::Converter, WebCore::IDLInterface > >::convert(JSC::ExecState&, JSC::JSValue)::{lambda(auto:1&&)#1} >&&)"); + + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String name = + "for_each_args,WebCore::IDLInterface>>::convert(JSC::ExecState&,JSC::JSValue)::{lambda(auto:1&&)#1},brigand::type_>,WebCore::Converter,WebCore::IDLInterface>>::convert(JSC::ExecState&,JSC::JSValue)::{lambda(auto:1&&)#1}>>"; + assertName(object, name, "brigand"); + + String signature = object.getSignature(false); + assertEquals( + "WebCore::Converter,WebCore::IDLInterface>>::convert(JSC::ExecState&,JSC::JSValue)::{lambda(auto:1&&)#1} brigand::for_each_args,WebCore::IDLInterface>>::convert(JSC::ExecState&,JSC::JSValue)::{lambda(auto:1&&)#1},brigand::type_>,WebCore::Converter,WebCore::IDLInterface>>::convert(JSC::ExecState&,JSC::JSValue)::{lambda(auto:1&&)#1}>>(WebCore::Converter,WebCore::IDLInterface>>::convert(JSC::ExecState&,JSC::JSValue)::{lambda(auto:1&&)#1},brigand::type_> &&,WebCore::Converter,WebCore::IDLInterface>>::convert(JSC::ExecState&,JSC::JSValue)::{lambda(auto:1&&)#1}> &&)", + signature); + } + + @Test + public void testFunctionWithLambdaParameter() throws Exception { + + // + // Mangled: _ZN3JSC9Structure3addILNS0_9ShouldPinE1EZNS_8JSObject35prepareToPutDirectWithoutTransitionERNS_2VMENS_12PropertyNameEjjPS0_EUlRKNS_24GCSafeConcurrentJSLockerEiiE_EEiS5_S6_jRKT0_ + // + // Demangled: int + // JSC::Structure::add< + // (JSC::Structure::ShouldPin)1, + // JSC::JSObject::prepareToPutDirectWithoutTransition(JSC::VM&, JSC::PropertyName, unsigned int, unsigned int, JSC::Structure*)::{lambda(JSC::GCSafeConcurrentJSLocker const&, int, int)#1} + // >( + // JSC::VM&, JSC::PropertyName, + // unsigned int, + // JSC::JSObject::prepareToPutDirectWithoutTransition(JSC::VM&, JSC::PropertyName, unsigned int, unsigned int, JSC::Structure*)::{lambda(JSC::GCSafeConcurrentJSLocker const&, int, int)#1} const& + // ) + // + // + + DemangledObject object = parser.parse( + "_ZN3JSC9Structure3addILNS0_9ShouldPinE1EZNS_8JSObject35prepareToPutDirectWithoutTransitionERNS_2VMENS_12PropertyNameEjjPS0_EUlRKNS_24GCSafeConcurrentJSLockerEiiE_EEiS5_S6_jRKT0_", + "int JSC::Structure::add<(JSC::Structure::ShouldPin)1, JSC::JSObject::prepareToPutDirectWithoutTransition(JSC::VM&, JSC::PropertyName, unsigned int, unsigned int, JSC::Structure*)::{lambda(JSC::GCSafeConcurrentJSLocker const&, int, int)#1}>(JSC::VM&, JSC::PropertyName, unsigned int, JSC::JSObject::prepareToPutDirectWithoutTransition(JSC::VM&, JSC::PropertyName, unsigned int, unsigned int, JSC::Structure*)::{lambda(JSC::GCSafeConcurrentJSLocker const&, int, int)#1} const&)"); + assertNotNull(object); + assertType(object, DemangledFunction.class); + + String name = + "add<(JSC::Structure::ShouldPin)1,JSC::JSObject::prepareToPutDirectWithoutTransition(JSC::VM&,JSC::PropertyName,unsigned_int,unsigned_int,JSC::Structure*)::{lambda(JSC::GCSafeConcurrentJSLocker_const&,int,int)#1}>"; + assertName(object, name, "JSC", "Structure"); + + String signature = object.getSignature(false); + assertEquals( + "int JSC::Structure::add<(JSC::Structure::ShouldPin)1,JSC::JSObject::prepareToPutDirectWithoutTransition(JSC::VM&,JSC::PropertyName,unsigned_int,unsigned_int,JSC::Structure*)::{lambda(JSC::GCSafeConcurrentJSLocker_const&,int,int)#1}>(JSC::VM &,JSC::PropertyName,unsigned int,JSC::JSObject::prepareToPutDirectWithoutTransition(JSC::VM&,JSC::PropertyName,unsigned_int,unsigned_int,JSC::Structure*)::{lambda(JSC::GCSafeConcurrentJSLocker const&, int, int)#1} const &)", + signature); + } + @Test public void testFunctionInLambdaNamespace() throws Exception { diff --git a/Ghidra/Features/PDB/src/main/java/pdb/AskPdbOptionsDialog.java b/Ghidra/Features/PDB/src/main/java/pdb/AskPdbOptionsDialog.java index 02ee462e1b..a422254ad7 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/AskPdbOptionsDialog.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/AskPdbOptionsDialog.java @@ -65,7 +65,7 @@ class AskPdbOptionsDialog extends DialogComponentProvider { combo.setSelectedIndex(0); restrictionsCombo.setEnabled(!useMsDiaParser); combo.addActionListener(e -> { - useMsDiaParser = (combo.getSelectedIndex() == 0); + useMsDiaParser = (combo.getSelectedIndex() == 1); restrictionsCombo.setEnabled(!useMsDiaParser); if (useMsDiaParser) { restrictionsCombo.setSelectedItem(PdbApplicatorRestrictions.NONE); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/SymbolPathParser.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/SymbolPathParser.java index ede72c9508..412017fc96 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/SymbolPathParser.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/SymbolPathParser.java @@ -32,8 +32,6 @@ import ghidra.program.model.symbol.Namespace; */ public class SymbolPathParser { - private static String ANONYMOUS_NAMESPACE = "(anonymous_namespace)"; - /** * Parses a String pathname into its constituent namespace and name components. * The list does not contain the global namespace, which is implied, but then @@ -43,12 +41,26 @@ public class SymbolPathParser { * @return {@literal List} containing the sequence of namespaces and trailing name. */ public static List parse(String name) { + return parse(name, true); + } + + /** + * Parses a String pathname into its constituent namespace and name components. + * The list does not contain the global namespace, which is implied, but then + * has each more deeply nested namespace contained in order in the list, followed + * by the trailing name. + * @param name The input String to be parsed. + * @param ignoreLeaderParens true signals to ignore any string that starts with a '(' char. + * This is useful to work around some problem characters. + * @return {@literal List} containing the sequence of namespaces and trailing name. + */ + public static List parse(String name, boolean ignoreLeaderParens) { if (StringUtils.isBlank(name)) { throw new IllegalArgumentException( "Symbol list must contain at least one symbol name!"); } - if (skipParsing(name)) { + if (skipParsing(name, ignoreLeaderParens)) { List list = new ArrayList<>(); list.add(name); return list; @@ -56,17 +68,14 @@ public class SymbolPathParser { return naiveParse(name); } - private static boolean skipParsing(String name) { + private static boolean skipParsing(String name, boolean ignoreLeaderParens) { // if (name.indexOf(Namespace.DELIMITER) == -1) { // following is temporary kludge due to struct (blah). TODO: figure/fix // This particular test for starting with the open parenthesis is to work around a type // seen in "Rust." - if (name.startsWith("(")) { - // anonymous namespace is a gnu c++ construct. We do not have any way of modeling - // this yet, but still wish not to lose this information, so we do not strip it out of - // the name when parsing gnu demangled symbols. - return !name.startsWith(ANONYMOUS_NAMESPACE); + if (ignoreLeaderParens && name.startsWith("(")) { + return true; } return !name.contains(Namespace.DELIMITER); diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/demangler/gnu/GnuDemanglerIntegrationTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/demangler/gnu/GnuDemanglerIntegrationTest.java index 5de5575264..cee9fe64fa 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/demangler/gnu/GnuDemanglerIntegrationTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/util/demangler/gnu/GnuDemanglerIntegrationTest.java @@ -101,6 +101,37 @@ public class GnuDemanglerIntegrationTest extends AbstractGhidraHeadlessIntegrati assertNotNull(cmd.getDemangledObject()); } + @Test + public void testParsingFunctionWithLambdaParameter() throws Exception { + + // + // This shows a bug when applying a function that has as one of its parameters a lambda function + // + + String mangled = + "_ZN3JSC9Structure3addILNS0_9ShouldPinE1EZNS_8JSObject35prepareToPutDirectWithoutTransitionERNS_2VMENS_12PropertyNameEjjPS0_EUlRKNS_24GCSafeConcurrentJSLockerEiiE_EEiS5_S6_jRKT0_"; + + GnuDemangler demangler = new GnuDemangler(); + demangler.canDemangle(program);// this performs initialization + + GnuDemanglerOptions options = new GnuDemanglerOptions(); + options.setDemangleOnlyKnownPatterns(false); + options = options.withDeprecatedDemangler(); + DemangledObject result = demangler.demangle(mangled, options); + assertNotNull(result); + assertEquals( + "int JSC::Structure::add<(JSC::Structure::ShouldPin)1,JSC::JSObject::prepareToPutDirectWithoutTransition(JSC::VM&,JSC::PropertyName,unsigned_int,unsigned_int,JSC::Structure*)::{lambda(JSC::GCSafeConcurrentJSLocker_const&,int,int)#1}>(JSC::VM &,JSC::PropertyName,unsigned int,JSC::JSObject::prepareToPutDirectWithoutTransition(JSC::VM&,JSC::PropertyName,unsigned_int,unsigned_int,JSC::Structure*)::{lambda(JSC::GCSafeConcurrentJSLocker const&, int, int)#1} const &)", + result.getSignature(false)); + + DemanglerCmd cmd = new DemanglerCmd(addr("01001000"), mangled, options); + + // this used to trigger an exception + boolean success = applyCmd(program, cmd); + assertTrue("Demangler command failed: " + cmd.getStatusMsg(), success); + + assertNotNull(cmd.getDemangledObject()); + } + private Address addr(String address) { return program.getAddressFactory().getAddress(address); }