| /******************************************************************************* |
| * Copyright (c) 2010 Nokia and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Nokia - Initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.cdt.debug.edc.internal.symbols.files; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Stack; |
| import java.util.WeakHashMap; |
| |
| import org.eclipse.cdt.debug.edc.symbols.IUnmangler; |
| |
| /** |
| * Unmangler for the ARM/Itanium/etc. EABI (http://www.codesourcery.com/public/cxx-abi/abi.html) |
| * <p> |
| * TODO: <expression> <closure-type-name> <lambda-sig> |
| */ |
| public class UnmanglerEABI implements IUnmangler { |
| |
| private static boolean DEBUG = false; |
| |
| enum SubstType { |
| PREFIX, |
| TEMPLATE_PREFIX, |
| TYPE, |
| QUAL_TYPE, |
| TEMPLATE_TEMPLATE_PARAM, |
| |
| } |
| public UnmanglerEABI() { |
| |
| } |
| |
| static class UnmangleState { |
| private char[] symbol; |
| private int index; |
| StringBuilder buffer; |
| private Stack<Integer> pushes ; // lengths of buffer when pushed |
| private List<String> substitutions; |
| private Map<Integer, SubstType> substitutionTypes; |
| private int lastTypeNameIndex; |
| |
| private List<String> templateArgs; |
| private int templateArgBase; |
| private Stack<Integer> templateArgStack; // length of templateArgs when pushed |
| |
| private Stack<Integer> backtracks ; // grouped entries: index value, lengths of buffer, and substitutions length when pushed |
| |
| private final boolean nameOnly; |
| |
| public UnmangleState(String symbol, boolean nameOnly) { |
| this.symbol = symbol.toCharArray(); |
| this.nameOnly = nameOnly; |
| index = 0; |
| buffer = new StringBuilder(); |
| pushes = new Stack<Integer>(); |
| substitutions = new ArrayList<String>(); |
| substitutionTypes = new HashMap<Integer, UnmanglerEABI.SubstType>(); |
| templateArgs = new ArrayList<String>(); |
| templateArgStack = new Stack<Integer>(); |
| backtracks = new Stack<Integer>(); |
| lastTypeNameIndex = -1; |
| } |
| |
| /* (non-Javadoc) |
| * @see java.lang.Object#toString() |
| */ |
| @Override |
| public String toString() { |
| String remaining = getRemaining(); |
| if (remaining.length() == 0) |
| remaining = "<<end>>"; |
| return "state: at [" + remaining + "], so far: " + current(); |
| } |
| |
| /** |
| * Push when entering a new decoding context (BNF expression). |
| */ |
| public void push() { |
| pushes.push(buffer.length()); |
| } |
| |
| /** |
| * Pop the current decoded string and restore context to |
| * the calling context. |
| * @return decoded string |
| */ |
| public String pop() { |
| int oldpos = pushes.isEmpty() ? 0 : pushes.pop(); |
| String str = buffer.substring(oldpos, buffer.length()); |
| buffer.setLength(oldpos); |
| return str; |
| } |
| |
| /** |
| * Push template argument state |
| */ |
| public void pushTemplateArgs() { |
| templateArgStack.push(templateArgBase); |
| templateArgStack.push(templateArgs.size()); |
| } |
| |
| /** |
| * Pop template argument state |
| * @throws UnmanglingException |
| */ |
| public void popTemplateArgs() throws UnmanglingException { |
| try { |
| templateArgs.subList(templateArgStack.pop(), templateArgs.size()).clear(); |
| templateArgBase = templateArgStack.pop(); |
| } catch (Exception e) { |
| throw new UnmanglingException("template stack empty", buffer.toString()); |
| } |
| } |
| /** |
| * Push all state, when entering a possible backtrack scenario. |
| * Use #safePop() if an operation succeeds, or #safeBacktrack() |
| * if it failed and you want to retry. |
| */ |
| public void safePush() { |
| backtracks.push(index); |
| backtracks.push(lastTypeNameIndex); |
| backtracks.push(buffer.length()); |
| backtracks.push(substitutions.size()); |
| backtracks.push(pushes.size()); |
| } |
| |
| /** |
| * Call when a #safePush() branch has succeeded to discard backtrack state. |
| */ |
| public void safePop() { |
| backtracks.pop(); |
| backtracks.pop(); |
| backtracks.pop(); |
| backtracks.pop(); |
| backtracks.pop(); |
| } |
| |
| /** |
| * Call when a #safePush() branch has failed to reset backtrack state. |
| * (To perform another backtrack, call #safePush() again) |
| */ |
| public void safeBacktrack() { |
| int oldSize = backtracks.pop(); |
| pushes.subList(oldSize, pushes.size()).clear(); |
| oldSize = backtracks.pop(); |
| substitutions.subList(oldSize, substitutions.size()).clear(); |
| while (substitutionTypes.size() > oldSize) |
| substitutionTypes.remove(substitutionTypes.size() - 1); |
| buffer.setLength(backtracks.pop()); |
| lastTypeNameIndex = backtracks.pop(); |
| index = backtracks.pop(); |
| } |
| |
| /** |
| * Tell if there is any current string (length > 0) |
| * @return |
| */ |
| public boolean hasCurrent() { |
| int oldpos = pushes.isEmpty() ? 0 : pushes.peek(); |
| int end = buffer.length(); |
| return end > oldpos; |
| } |
| |
| /** |
| * Get the current constructed string (since the last #push()) |
| * @return |
| */ |
| public String current() { |
| int oldpos = pushes.isEmpty() ? 0 : pushes.peek(); |
| String str = buffer.substring(oldpos, buffer.length()); |
| return str; |
| } |
| |
| /** |
| * Remember the current constructed string as a substitution. |
| * @param substType |
| */ |
| public void remember(SubstType substType) { |
| remember(current(), substType); |
| } |
| |
| public boolean lastSubstitutionIsPrefix(SubstType substType) { |
| if (substitutions.size() == 0) |
| return false; |
| String current = current(); |
| if (substitutions.get(substitutions.size() - 1).length() >= current.length()) |
| return false; |
| return lastSubstitution() == substType; |
| } |
| /** |
| * Remember the given string as a substitution. |
| * @param name |
| * @param substType |
| */ |
| public void remember(String name, SubstType substType) { |
| if (name.length() == 0) |
| return; |
| int num = substitutions.size(); |
| if (num > 0 && substitutions.get(num - 1).equals(name)) |
| return; |
| substitutions.add(name); |
| substitutionTypes.put(num, substType); |
| lastTypeNameIndex = num; |
| if (DEBUG) System.out.println(num+" := " + name + " --> " + substType); |
| } |
| |
| /** |
| * Replace the last substitution. |
| * @param name |
| * @param substType |
| */ |
| public void rememberInstead(String name, SubstType substType) { |
| int num = substitutions.size() - 1; |
| substitutions.set(num, name); |
| substitutionTypes.put(num, substType); |
| if (DEBUG) System.out.println(num+" ::= " + name + " -- > " + substType); |
| } |
| |
| /** |
| * Pop the current decoded string as in {@link #pop()} |
| * and remember the string as a substitution. |
| * @return String |
| */ |
| public String popAndRemember(SubstType substType) { |
| String name = pop(); |
| remember(name, substType); |
| return name; |
| } |
| |
| public char peek() { |
| return index < symbol.length ? symbol[index] : 0; |
| } |
| |
| public char peek(int offset) { |
| return index + offset < symbol.length ? symbol[index + offset] : 0; |
| } |
| |
| public void consume(char ch) throws UnmanglingException { |
| if (ch != get()) |
| throw unexpected(); |
| } |
| public char get() { |
| return index < symbol.length ? symbol[index++] : 0; |
| } |
| |
| public void unget() { |
| if (index > 0) index--; |
| } |
| public void skip() { |
| if (index < symbol.length) |
| index++; |
| } |
| |
| public void skip2() { |
| index = Math.min(index + 2, symbol.length); |
| } |
| |
| public boolean done() { |
| return index >= symbol.length; |
| } |
| |
| public UnmanglingException unexpected() { |
| return new UnmanglingException("Unexpected text at " + getRemaining(), buffer.toString()); |
| } |
| public UnmanglingException unexpected(String what) { |
| return new UnmanglingException("Wanted " + what + " but got unexpected text at " + getRemaining(), buffer.toString()); |
| } |
| public UnmanglingException notImplemented() { |
| return new UnmanglingException("Unimplemented at " + getRemaining(), |
| buffer.toString()); |
| } |
| |
| /** |
| * @return |
| */ |
| private String getRemaining() { |
| if (index >= symbol.length) |
| return ""; |
| return new String(symbol, index, symbol.length - index); |
| } |
| |
| /** |
| * @throws UnmanglingException |
| * |
| */ |
| public void throwIfDone() throws UnmanglingException { |
| if (done()) |
| throw new UnmanglingException("Unexpected end of symbol", |
| buffer.toString()); |
| } |
| |
| public void updateSubstitution(SubstType substType) { |
| int num = substitutions.size() - 1; |
| substitutionTypes.put(num, substType); |
| if (DEBUG) System.out.println(num + " ::= " + substType); |
| } |
| |
| /** |
| * @return |
| */ |
| public SubstType lastSubstitution() { |
| return substitutionTypes.get(substitutions.size() - 1); |
| } |
| |
| /** |
| * @param arg |
| */ |
| public void rememberTemplateArg(String arg) { |
| templateArgs.add(arg); |
| } |
| |
| /** |
| * @param num |
| * @return |
| * @throws UnmanglingException |
| */ |
| public String getTemplateArg(int num) throws UnmanglingException { |
| num -= templateArgBase; |
| if (num < 0 || num >= templateArgs.size()) |
| throw unexpected("template argument in range 0-" + (templateArgs.size() - templateArgBase)+"; got " + num); |
| return templateArgs.get(num); |
| } |
| |
| public String lastSubstitutedName() { |
| if (lastTypeNameIndex < 0) |
| return ""; |
| return substitutions.get(lastTypeNameIndex); |
| } |
| } |
| |
| private static WeakHashMap<String, String> unmangledMap = new WeakHashMap<String, String>(); |
| private static WeakHashMap<String, String> withoutArgsMap = new WeakHashMap<String, String>(); |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.cdt.debug.edc.internal.symbols.files.IUnmangler#undecorate(java.lang.String) |
| */ |
| public String undecorate(String symbol) { |
| // symbols may have @@GLIBC... type suffixes |
| int atat = symbol.indexOf("@@"); |
| if (atat > 0) |
| symbol = symbol.substring(0, atat); |
| return symbol; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.cdt.debug.edc.internal.symbols.files.IUnmangler#isMangled(java.lang.String) |
| */ |
| public boolean isMangled(String symbol) { |
| if (symbol == null) |
| return false; |
| if (symbol.startsWith("_Z")) |
| return true; |
| // this is used for enum constants |
| if (symbol.startsWith("__N")) |
| return true; |
| return false; |
| } |
| |
| public String unmangleWithoutArgs(String symbol) throws UnmanglingException { |
| return unmangle(symbol, true); |
| } |
| |
| public String unmangle(String symbol) throws UnmanglingException { |
| return unmangle(symbol, false); |
| } |
| |
| public String unmangleType(String symbol) throws UnmanglingException { |
| if (symbol == null) |
| return null; |
| |
| if (unmangledMap.containsKey(symbol)) |
| return unmangledMap.get(symbol); |
| |
| if (symbol.startsWith("_Z")) { |
| UnmangleState state = new UnmangleState(symbol, false); |
| state.skip2(); |
| String unmangled = ""; |
| if (state.peek() == 'S') { |
| unmangled += unmangleSubstitution(state); |
| } |
| while (!state.done()) { |
| if (state.peek() == 'I') { |
| // unscoped-template-name |
| state.remember(unmangled, SubstType.TEMPLATE_PREFIX); |
| String args = unmangleTemplateArgs(state, false); |
| state.buffer.append(args); |
| unmangled += args; |
| } else { |
| if (unmangled.equals("::std")) |
| unmangled += "::"; |
| unmangled += unmangleType(state); |
| } |
| state.remember(unmangled, SubstType.TYPE); |
| } |
| unmangledMap.put(symbol, unmangled); |
| return unmangled; |
| } |
| return symbol; |
| } |
| |
| public String unmangle(String symbol, boolean skipArgs) throws UnmanglingException { |
| if (symbol == null) |
| return null; |
| |
| String unmangled; |
| |
| if (skipArgs) { |
| if (withoutArgsMap.containsKey(symbol)) |
| unmangled = withoutArgsMap.get(symbol); |
| else { |
| unmangled = doUnmangle(symbol, true); |
| withoutArgsMap.put(symbol, unmangled); |
| } |
| } else if (unmangledMap.containsKey(symbol)) { |
| unmangled = unmangledMap.get(symbol); |
| } else { |
| unmangled = doUnmangle(symbol, skipArgs); |
| unmangledMap.put(symbol, unmangled); |
| |
| do {// for break below if conditionals succeed |
| int paren = unmangled.indexOf('('); |
| if (0 < paren) { |
| String unmangledWithoutArgs = unmangled.substring(0, paren-1); |
| if (unmangledWithoutArgs != null && unmangledWithoutArgs.length() != 0) { |
| withoutArgsMap.put(symbol, unmangledWithoutArgs); |
| break; |
| } } |
| withoutArgsMap.put(symbol, unmangled); |
| } while (false);// allows break above to skip default case |
| } |
| |
| return unmangled; |
| } |
| |
| /** |
| * @param symbol |
| * @return |
| * @throws UnmanglingException |
| */ |
| private String doUnmangle(String symbol, boolean nameOnly) throws UnmanglingException { |
| /* |
| Entities with C linkage and global namespace variables are not mangled. Mangled names have the general structure: |
| |
| |
| <mangled-name> ::= _Z <encoding> |
| <encoding> ::= <function name> <bare-function-type> |
| ::= <data name> |
| ::= <special-name> |
| */ |
| if (symbol.startsWith("_Z")) { |
| String suffix = ""; |
| int idx = symbol.indexOf('@'); |
| if (idx >= 0) { |
| suffix = symbol.substring(idx); |
| symbol = symbol.substring(0, idx); |
| } |
| |
| UnmangleState state = new UnmangleState(symbol, nameOnly); |
| state.skip2(); |
| |
| String unmangled = unmangleEncoding(state); |
| unmangled += suffix; |
| return unmangled; |
| } else if (symbol.startsWith("__N")) { |
| UnmangleState state = new UnmangleState(symbol, true); |
| state.skip2(); |
| |
| String unmangled = unmangleName(state); |
| return unmangled; |
| } else { |
| return symbol; |
| } |
| } |
| |
| /* |
| <encoding> ::= <function name> <bare-function-type> |
| ::= <data name> |
| ::= <special-name> |
| */ |
| private String unmangleEncoding(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| |
| String name; |
| |
| // ferret out <special-name> |
| char ch = state.peek(); |
| if (ch == 'T' || ch == 'G') { |
| name = unmangleSpecialName(state); |
| } else { |
| name = unmangleName(state); |
| } |
| |
| if (!state.done() && !state.nameOnly) { |
| boolean isTemplate = name.endsWith(">"); // HACK |
| if (isTemplate) { |
| state.buffer.append(unmangleType(state)); |
| state.buffer.append(' '); |
| } |
| state.buffer.append(name); |
| state.buffer.append(unmangleBareFunctionType(state, false)); |
| } else { |
| state.buffer.append(name); |
| } |
| |
| return state.pop(); |
| } |
| |
| private void unmangleSpecialNameCallOffset(UnmangleState state, char ch) |
| throws UnmanglingException { |
| |
| switch (ch) { |
| case 'h': { |
| // h <nv-offset> _ |
| int offset = doUnmangleNumber(state); |
| state.consume('_'); |
| state.buffer.append("<non-virtual base override at offset "); |
| appendHexNumber(state.buffer, offset); |
| break; |
| } |
| case 'v': { |
| // v <offset number> _ <virtual offset number> _ |
| int offset = doUnmangleNumber(state); |
| state.consume('_'); |
| int voffset = doUnmangleNumber(state); |
| state.consume('_'); |
| state.buffer.append("<virtual base override at offset "); |
| appendHexNumber(state.buffer, offset); |
| state.buffer.append(", vcall offset "); |
| appendHexNumber(state.buffer, voffset); |
| break; |
| } |
| default: |
| throw state.unexpected("special name call-offset"); |
| } |
| } |
| |
| /* |
| <special-name> ::= TV <type> # virtual table |
| ::= TT <type> # VTT structure (construction vtable index) |
| ::= TI <type> # typeinfo structure |
| ::= TS <type> # typeinfo name (null-terminated byte string) |
| <special-name> ::= GV <object name> # Guard variable for one-time initialization |
| # No <type> |
| <special-name> ::= T <call-offset> <base encoding> |
| # base is the nominal target function of thunk |
| <call-offset> ::= h <nv-offset> _ |
| ::= v <v-offset> _ |
| <nv-offset> ::= <offset number> |
| # non-virtual base override |
| <v-offset> ::= <offset number> _ <virtual offset number> |
| # virtual base override, with vcall offset |
| |
| <special-name> ::= Tc <call-offset> <call-offset> <base encoding> |
| # base is the nominal target function of thunk |
| # first call-offset is 'this' adjustment |
| # second call-offset is result adjustment |
| |
| */ |
| private String unmangleSpecialName(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| |
| char ch = state.get(); |
| if (ch == 'T') { |
| String type = null; |
| ch = state.get(); |
| switch (ch) { |
| case 'V': |
| type = unmangleType(state); |
| state.buffer.append("<virtual table for "); |
| state.buffer.append(type); |
| state.buffer.append('>'); |
| break; |
| case 'T': |
| type = unmangleType(state); |
| state.buffer.append("<VTT structure for "); |
| state.buffer.append(type); |
| state.buffer.append('>'); |
| break; |
| case 'I': |
| type = unmangleType(state); |
| state.buffer.append("<typeinfo structure for "); |
| state.buffer.append(type); |
| state.buffer.append('>'); |
| break; |
| case 'S': |
| type = unmangleType(state); |
| state.buffer.append("<typeinfo name for "); |
| state.buffer.append(type); |
| state.buffer.append('>'); |
| break; |
| case 'h': |
| case 'v': |
| unmangleSpecialNameCallOffset(state, ch); |
| state.buffer.append(" for "); |
| state.buffer.append(unmangleEncoding(state)); |
| state.buffer.append('>'); |
| break; |
| case 'c': { |
| // c <call-offset> <call-offset> <base encoding> |
| state.buffer.append("<covariant : 'this' adjustment "); |
| unmangleSpecialNameCallOffset(state, state.get()); |
| state.buffer.append("> result adjustment "); |
| unmangleSpecialNameCallOffset(state, state.get()); |
| state.buffer.append("> for "); |
| state.buffer.append(unmangleEncoding(state)); |
| state.buffer.append('>'); |
| break; |
| } |
| default: |
| throw state.unexpected("special name"); |
| } |
| } else if (ch == 'G') { |
| switch (state.get()) { |
| case 'V': |
| state.buffer.append("<one-time-init guard for "); |
| state.buffer.append(unmangleName(state)); |
| state.buffer.append('>'); |
| break; |
| default: |
| throw state.unexpected("special name"); |
| } |
| } |
| |
| return state.pop(); |
| } |
| |
| private void appendHexNumber(StringBuilder builder, int offset) { |
| if (offset < 0) { |
| builder.append("-0x"); |
| builder.append(Integer.toHexString(-offset)); |
| } else { |
| builder.append("0x"); |
| builder.append(Integer.toHexString(offset)); |
| } |
| } |
| |
| /** |
| * @param state |
| * @param name |
| * @return |
| * @throws UnmanglingException |
| */ |
| private String doUnmangleFunctionWithName(UnmangleState state, boolean expectReturn, String name) throws UnmanglingException { |
| state.push(); |
| |
| state.consume('F'); |
| |
| if (expectReturn) { |
| state.buffer.append(unmangleType(state)); |
| state.buffer.append(' '); |
| } |
| |
| if (name != null) |
| state.buffer.append(name); |
| |
| state.buffer.append(unmangleBareFunctionType(state, false)); |
| |
| state.consume('E'); |
| |
| return state.pop(); |
| } |
| |
| /** |
| * @param state |
| * @param expectReturn true if a return type precedes argument list |
| * @throws UnmanglingException |
| */ |
| private String unmangleBareFunctionType(UnmangleState state, boolean expectReturn) throws UnmanglingException { |
| state.push(); |
| if (expectReturn) { |
| state.buffer.append(unmangleType(state)); |
| state.buffer.append(' '); |
| } |
| state.buffer.append('('); |
| if (state.peek() == 'v') { |
| state.skip(); |
| } else { |
| boolean first = true; |
| while (!state.done() && state.peek() != 'E') { |
| if (first) { |
| first = false; |
| } else { |
| state.buffer.append(','); |
| } |
| state.buffer.append(unmangleType(state)); |
| } |
| } |
| state.buffer.append(')'); |
| return state.pop(); |
| } |
| |
| /* |
| <name> ::= <nested-name> = N ... |
| ::= <unscoped-name> = number or St ... |
| ::= <unscoped-template-name> <template-args> = unscoped | S ... | I ... |
| ::= <local-name> # See Scope Encoding below = Z ... |
| |
| */ |
| private String unmangleName(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| char ch = state.peek(); |
| if (ch == 'N') { |
| state.buffer.append(unmangleNestedName(state, true)); |
| } else if (ch == 'Z') { |
| state.buffer.append(unmangleLocalName(state)); |
| } else if (ch == 0) { |
| state.throwIfDone(); |
| } else { |
| // must be unscoped-name or unscoped-template-name |
| |
| if (ch == 'S' && state.peek(1) == 't') { |
| state.skip2(); |
| state.buffer.append("::std::"); |
| } |
| String name = unmangleUnqualifiedName(state); |
| state.buffer.append(name); |
| if (state.peek() == 'I') { |
| // unscoped-template-name |
| state.remember(name, SubstType.TEMPLATE_PREFIX); |
| String args = unmangleTemplateArgs(state, false); |
| state.buffer.append(args); |
| state.remember(name + args, SubstType.TYPE); |
| } |
| } |
| return state.pop(); |
| } |
| |
| /* |
| <local-name> := Z <function encoding> E <entity name> [<discriminator>] |
| := Z <function encoding> E s [<discriminator>] |
| |
| <discriminator> := _ <non-negative number> # when number < 10 |
| := __ <non-negative number> _ # when number >= 10 |
| |
| */ |
| private String unmangleLocalName(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| state.consume('Z'); |
| state.buffer.append(unmangleEncoding(state)); |
| state.consume('E'); |
| |
| boolean isStringLiteral = false; |
| if (state.peek() == 's') { |
| isStringLiteral = true; |
| state.skip(); |
| if (state.peek() == '_') |
| state.buffer.append("::"); |
| } else { |
| addNameWithColons(state, unmangleName(state)); |
| } |
| if (state.peek() == '_') { |
| state.skip(); |
| int num; |
| if (state.peek() == '_') { |
| // >= 10 |
| num = doUnmangleNonNegativeNumber(state); |
| state.consume('_'); |
| } else { |
| char ch = state.get(); |
| if (ch >= '0' && ch <= '9') { |
| num = ch - '0'; |
| } else { |
| throw state.unexpected("number"); |
| } |
| } |
| if (isStringLiteral) |
| state.buffer.append("string literal"); |
| state.buffer.append("#" + num); |
| } |
| return state.pop(); |
| } |
| |
| /* |
| <source-name> ::= <positive length number> <identifier> |
| <number> ::= [n] <non-negative decimal integer> |
| <identifier> ::= <unqualified source code identifier> |
| */ |
| private String unmangleSourceName(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| char ch = state.peek(); |
| if (ch >= '0' && ch <= '9') { |
| int length = doUnmangleNumber(state); |
| while (length-- > 0) { |
| state.throwIfDone(); |
| state.buffer.append(state.get()); |
| } |
| return state.pop(); |
| } else { |
| throw state.unexpected(); |
| } |
| } |
| |
| /* |
| * [0-9]+ |
| */ |
| private int doUnmangleNonNegativeNumber(UnmangleState state) { |
| int number = 0; |
| char ch; |
| while ((ch = state.get()) != 0 && ch >= '0' && ch <= '9') { |
| number = number * 10 + (ch - '0'); |
| } |
| state.unget(); |
| return number; |
| } |
| |
| /* |
| * [n] <non-negative decimal number> |
| */ |
| private int doUnmangleNumber(UnmangleState state) { |
| boolean neg = false; |
| if (state.peek() == 'n') { |
| state.skip(); |
| neg = true; |
| } |
| int number = doUnmangleNonNegativeNumber(state); |
| return neg ? -number : number; |
| } |
| |
| /* |
| <nested-name> ::= N [<CV-qualifiers>] <prefix> <unqualified-name> E |
| ::= N [<CV-qualifiers>] <template-prefix> <template-args> E (args = I...) |
| |
| */ |
| private String unmangleNestedName(UnmangleState state, boolean allowCV) |
| throws UnmanglingException { |
| |
| state.push(); |
| |
| state.consume('N'); |
| |
| String cvquals = allowCV ? unmangleCVQualifiers(state) : null; |
| |
| state.buffer.append(unmanglePrefix(state, SubstType.PREFIX)); |
| |
| state.consume('E'); |
| |
| if (allowCV && (cvquals != null && cvquals.length() > 0)) { |
| state.buffer.append(' '); |
| state.buffer.append(cvquals); |
| } |
| return state.pop(); |
| } |
| |
| |
| /* |
| <template-args> ::= I <template-arg>+ E |
| |
| */ |
| private String unmangleTemplateArgs(UnmangleState state, boolean substArg) throws UnmanglingException { |
| state.push(); |
| |
| int origTypeIndex = state.lastTypeNameIndex; |
| |
| String typeName = state.lastSubstitutedName(); |
| |
| if (!substArg || state.peek() == 'I') { |
| state.consume('I'); |
| substArg = false; |
| } |
| state.buffer.append('<'); |
| boolean lastArgWasSubst = false; |
| if (state.peek() != 'E') { |
| boolean first = true; |
| do { |
| if (first) |
| first = false; |
| else |
| state.buffer.append(','); |
| boolean unfinishedTemplateSubst = false; |
| if (state.peek() == 'S') { |
| char ch2 = state.peek(1); |
| if (ch2 == 't') { |
| state.buffer.append("::std::"); |
| state.skip2(); |
| first = true; |
| continue; // more of this arg to come |
| } |
| lastArgWasSubst = true; |
| if (ch2 == 'a' || ch2 == 'b') { |
| unfinishedTemplateSubst = true; |
| } |
| } |
| String arg = unmangleTemplateArg(state); |
| if (unfinishedTemplateSubst) |
| arg += unmangleTemplateArgs(state, false); |
| state.buffer.append(arg); |
| if (lastArgWasSubst && state.done()) |
| break; |
| lastArgWasSubst = false; |
| } while (state.peek() != 'E'); |
| } |
| |
| if (!substArg && !lastArgWasSubst) |
| state.consume('E'); |
| |
| if (state.buffer.lastIndexOf(">") == state.buffer.length() - 1) |
| state.buffer.append(' '); |
| state.buffer.append('>'); |
| |
| if (state.lastSubstitution() == SubstType.TEMPLATE_TEMPLATE_PARAM) |
| state.rememberInstead(typeName + state.current(), SubstType.TEMPLATE_TEMPLATE_PARAM); |
| else if (state.lastTypeNameIndex > origTypeIndex) |
| state.remember(typeName + state.current(), SubstType.TYPE); |
| state.lastTypeNameIndex = origTypeIndex; |
| |
| return state.pop(); |
| } |
| |
| /* |
| <template-arg> ::= <type> # type or template |
| ::= X <expression> E # expression |
| ::= <expr-primary> # simple expressions ('L') |
| ::= I <template-arg>* E # argument pack |
| ::= sp <expression> # pack expansion of (C++0x) |
| |
| */ |
| private String unmangleTemplateArg(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| |
| String arg = null; |
| char ch = state.peek(); |
| if (ch == 'X') { |
| throw state.notImplemented(); |
| } else if (ch == 'I') { |
| arg = unmangleTemplateArgs(state, false); |
| } else if (ch == 's' && state.peek(1) == 'p') { |
| throw state.notImplemented(); |
| } else if (ch == 'L') { |
| arg = unmangleExprPrimary(state); |
| } else { |
| arg = unmangleType(state); |
| } |
| state.rememberTemplateArg(arg); |
| state.buffer.append(arg); |
| |
| return state.pop(); |
| } |
| |
| |
| /** |
| <expr-primary> ::= L <type> <value number> E # integer literal |
| ::= L <type> <value float> E # floating literal |
| ::= L <string type> E # string literal |
| ::= L <nullptr type> E # nullptr literal (i.e., "LDnE") |
| ::= L <type> <real-part float> _ <imag-part float> E # complex floating point literal (C 2000) |
| ::= L <mangled-name> E # external name |
| |
| * @param state |
| * @return |
| */ |
| private String unmangleExprPrimary(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| state.consume('L'); |
| |
| try { |
| state.safePush(); |
| |
| String type = null; |
| String suffix = null; |
| switch (state.peek()) { |
| case 'i': // int |
| suffix = ""; |
| break; |
| case 'j': // unsigned int |
| suffix = "U"; |
| break; |
| case 'l': // long |
| suffix = "L"; |
| break; |
| case 'm': // unsigned long |
| suffix = "UL"; |
| break; |
| case 'x': // long long |
| suffix = "LL"; |
| break; |
| case 'y': // unsigned long long |
| suffix = "ULL"; |
| break; |
| } |
| if (suffix != null) { |
| state.skip(); |
| state.buffer.append(doUnmangleNumber(state)); |
| state.buffer.append(suffix); |
| } else { |
| // show other types |
| type = unmangleType(state); |
| state.buffer.append('('); |
| state.buffer.append(type); |
| state.buffer.append(')'); |
| state.buffer.append(doUnmangleNumber(state)); |
| } |
| state.safePop(); |
| } catch (UnmanglingException e) { |
| state.safeBacktrack(); |
| |
| // must be mangled-name or something else |
| state.buffer.append(unmangleName(state)); |
| } |
| state.consume('E'); |
| |
| return state.popAndRemember(SubstType.TEMPLATE_TEMPLATE_PARAM); |
| |
| } |
| /* |
| <template-param> ::= T_ # first template parameter |
| ::= T <parameter-2 non-negative number> _ |
| |
| */ |
| private String unmangleTemplateParam(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| |
| state.consume('T'); |
| int num = doUnmangleBase10(state); |
| state.buffer.append(state.getTemplateArg(num)); |
| |
| return state.popAndRemember(SubstType.TEMPLATE_TEMPLATE_PARAM); |
| } |
| |
| /** |
| * Base-10, where _ = 0 and 1..x = 0..x-1 |
| * @param state |
| * @return |
| * @throws UnmanglingException |
| */ |
| private int doUnmangleBase10(UnmangleState state) throws UnmanglingException { |
| char ch; |
| if (state.peek() == '_') { |
| state.skip(); |
| return 0; |
| } |
| int num = 0; |
| while ((ch = state.get()) != '_') { |
| state.throwIfDone(); |
| num = (num * 10) + (ch - '0'); |
| } |
| return num + 1; |
| } |
| |
| /* |
| <substitution> ::= S <seq-id> _ |
| ::= S_ |
| |
| <substitution> ::= St # ::std:: |
| <substitution> ::= Sa # ::std::allocator |
| <substitution> ::= Sb # ::std::basic_string |
| <substitution> ::= Ss # ::std::basic_string < char, |
| ::std::char_traits<char>, |
| ::std::allocator<char> > |
| <substitution> ::= Si # ::std::basic_istream<char, std::char_traits<char> > |
| <substitution> ::= So # ::std::basic_ostream<char, std::char_traits<char> > |
| <substitution> ::= Sd # ::std::basic_iostream<char, std::char_traits<char> > |
| |
| */ |
| private String unmangleSubstitution(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| state.consume('S'); |
| |
| char ch = state.peek(); |
| if (ch == '_' || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z')) { |
| int num = doUnmangleBase36(state); |
| if (num < 0 || num >= state.substitutions.size()) |
| throw state.unexpected("substitution id in the range 0-"+ state.substitutions.size() + " but got " + num); |
| String val = state.substitutions.get(num); |
| |
| SubstType type = state.substitutionTypes.get(num); |
| switch (type) { |
| case PREFIX: |
| //...? |
| state.buffer.append(val); |
| break; |
| case TEMPLATE_PREFIX: |
| state.buffer.append(val); |
| state.buffer.append(unmangleTemplateArgs(state, true)); |
| break; |
| case TEMPLATE_TEMPLATE_PARAM: |
| state.buffer.append(val); |
| break; |
| case QUAL_TYPE: |
| case TYPE: |
| // ...? |
| state.buffer.append(val); |
| break; |
| } |
| } else { |
| switch (ch) { |
| case 't': |
| state.buffer.append("::std"); break; |
| case 'a': |
| state.buffer.append("::std::allocator"); break; |
| case 'b': |
| state.buffer.append("::std::basic_string"); break; |
| case 's': |
| state.buffer.append("::std::basic_string<char,::std::char_traits<char>,::std::allocator<char> >"); break; |
| case 'i': |
| state.buffer.append("::std::basic_istream<char,::std::char_traits<char> >"); break; |
| case 'o': |
| state.buffer.append("::std::basic_ostream<char,::std::char_traits<char> >"); break; |
| case 'd': |
| state.buffer.append("::std::basic_iostream<char,::std::char_traits<char> >"); break; |
| default: |
| throw state.unexpected("std:: substitution"); |
| } |
| state.skip(); |
| } |
| |
| return state.pop(); |
| } |
| |
| /** |
| * As a special case, the first substitutable entity is encoded as "S_", |
| * i.e. with no number, so the numbered entities are the second one as |
| * "S0_", the third as "S1_", the twelfth as "SA_", the thirty-eighth as |
| * "S10_", etc. |
| * @throws UnmanglingException |
| */ |
| private int doUnmangleBase36(UnmangleState state) throws UnmanglingException { |
| int num = 0; |
| char ch = state.peek(); |
| if (ch == '_') { |
| state.skip(); |
| return 0; |
| } |
| while ((ch = state.get()) != '_') { |
| state.throwIfDone(); |
| num = (num * 10); |
| if (ch >= '0' && ch <= '9') |
| num += (ch - '0'); |
| else if (ch >= 'A' && ch <= 'Z') |
| num += (ch - 'A') + 10; |
| else |
| throw state.unexpected("BASE-36 number"); |
| } |
| return num + 1; |
| } |
| |
| /* |
| <prefix> ::= <prefix> <unqualified-name> # ... 0-9 |
| ::= <template-prefix> <template-args> --> template=T... args=I... |
| ::= <template-param> --> T... |
| ::= # empty |
| ::= <substitution> --> S... |
| ::= <prefix> <data-member-prefix> --> name M |
| |
| left-recursion elimination: |
| <prefix> ::= <template-prefix> <template-args> <prefix'> |
| ::= <template-param> <prefix'> |
| ::= <substitution> <prefix'> |
| ::= # empty |
| <prefix'> ::= <unqualified-name> <prefix'> |
| ::= <data-member-prefix> M <prefix'> |
| ::= #empty |
| */ |
| private String unmanglePrefix(UnmangleState state, SubstType substType) throws UnmanglingException { |
| state.push(); |
| |
| boolean any = false; |
| boolean lastSubst = false; |
| |
| while (true) { |
| char ch = state.peek(); |
| |
| if (ch == 'E') { |
| break; |
| } |
| |
| String part = null; |
| |
| if (ch == 'T') { |
| part = unmangleTemplateParam(state); |
| state.remember(substType); |
| } |
| else if (ch == 'S') { |
| part = unmangleSubstitution(state); |
| lastSubst = true; |
| } |
| else if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') |
| || (ch == 'C' || ch == 'D' || ch == 'L')) { |
| part = unmangleUnqualifiedName(state); |
| } |
| else if (ch == 'I') { |
| if (!any) |
| throw state.unexpected(); |
| |
| if (state.hasCurrent()) { |
| state.updateSubstitution(SubstType.TEMPLATE_PREFIX); |
| part = state.current(); |
| } |
| String args = unmangleTemplateArgs(state, false); |
| state.buffer.append(args); |
| continue; |
| } |
| else { |
| throw state.unexpected(); |
| } |
| |
| lastSubst = false; |
| any = true; |
| |
| if (lastSubst) |
| any = true; |
| if (state.hasCurrent()) { |
| addNameWithColons(state, part); |
| } else { |
| state.buffer.append(part); |
| } |
| |
| if (ch != 'S' && state.peek() != 'E') { |
| state.remember(substType); |
| } |
| } |
| |
| |
| return state.pop(); |
| } |
| |
| /** |
| * @param state |
| * @param name |
| */ |
| private void addNameWithColons(UnmangleState state, String name) { |
| if (state.hasCurrent() && !name.startsWith("::")) |
| state.buffer.append("::"); |
| state.buffer.append(name); |
| } |
| |
| /* |
| <template-prefix> ::= <prefix> <template unqualified-name> ... 0-9 |
| ::= <template-param> # T* |
| ::= <substitution> # S* |
| --> followed by <template-args> (I) |
| |
| left-recursion elimination: |
| <template-prefix> ::= <template-param> <template-prefix'> # T* |
| ::= <substitution> <template-prefix'> # S* |
| <template-prefix'> ::= <template unqualified-name> <template-prefix'> ... 0-9 |
| ::= #empty |
| --> followed by <template-args> (I) |
| */ |
| String unmangleTemplatePrefix(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| |
| char ch = state.peek(); |
| if (ch == 'S') { |
| state.buffer.append(unmangleSubstitution(state)); |
| state.buffer.append(unmangleTemplatePrefixPrime(state)); |
| return state.pop(); |
| } |
| |
| if (ch == 'T') { |
| state.buffer.append(unmangleTemplateParam(state)); |
| state.buffer.append(unmangleTemplatePrefixPrime(state)); |
| } |
| |
| return state.popAndRemember(SubstType.TEMPLATE_PREFIX); |
| } |
| |
| private String unmangleTemplatePrefixPrime(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| while (true) { |
| try { |
| state.buffer.append(unmangleUnqualifiedName(state)); |
| if (state.peek() == 'I') { |
| // unscoped-template-name |
| state.buffer.append(unmangleTemplateArgs(state, false)); |
| } |
| } catch (UnmanglingException e) { |
| break; |
| } |
| } |
| |
| return state.pop(); |
| } |
| |
| /* |
| <type> ::= <builtin-type> = rVKPROCGU ... |
| ::= <function-type> = |
| ::= <class-enum-type> |
| ::= <array-type> |
| ::= <pointer-to-member-type> = M... |
| ::= <template-param> |
| ::= <template-template-param> <template-args> |
| ::= <substitution> # See Compression below |
| |
| <type> ::= <CV-qualifiers> <type> |
| ::= P <type> # pointer-to |
| ::= R <type> # reference-to |
| ::= O <type> # rvalue reference-to (C++0x) |
| ::= C <type> # complex pair (C 2000) |
| ::= G <type> # imaginary (C 2000) |
| ::= U <source-name> <type> # vendor extended type qualifier |
| |
| <CV-qualifiers> ::= [r] [V] [K] # restrict (C99), volatile, const |
| */ |
| private String unmangleType(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| char ch = state.get(); |
| switch (ch) { |
| // |
| // qualified types |
| // |
| case 'r': |
| case 'V': |
| case 'K': |
| state.unget(); |
| String cvquals = unmangleCVQualifiers(state); |
| state.buffer.append(unmangleType(state)); |
| if (cvquals.length() > 0) { |
| state.buffer.append(' '); |
| state.buffer.append(cvquals); |
| } |
| if (state.lastSubstitutionIsPrefix(SubstType.QUAL_TYPE)) |
| state.remember(SubstType.QUAL_TYPE); |
| return state.popAndRemember(SubstType.QUAL_TYPE); |
| case 'P': |
| state.buffer.append(unmangleType(state)); |
| if (state.lastSubstitutionIsPrefix(SubstType.QUAL_TYPE)) |
| state.remember(SubstType.QUAL_TYPE); |
| ptrOrRefize(state.buffer, "*"); |
| return state.popAndRemember(SubstType.QUAL_TYPE); |
| case 'R': |
| state.buffer.append(unmangleType(state)); |
| if (state.lastSubstitutionIsPrefix(SubstType.QUAL_TYPE)) |
| state.remember(SubstType.QUAL_TYPE); |
| ptrOrRefize(state.buffer, "&"); |
| return state.popAndRemember(SubstType.QUAL_TYPE); |
| case 'O': // rvalue reference-to |
| case 'C': // complex pair |
| case 'G': // imaginary |
| throw state.notImplemented(); |
| case 'U': // vendor extension |
| { |
| // TODO: assuming the extension precedes the type, |
| // e.g. int __declspec(dllimport) foo(); |
| state.buffer.append(unmangleSourceName(state)); |
| state.buffer.append(' '); |
| state.buffer.append(unmangleType(state)); |
| return state.popAndRemember(SubstType.TYPE); |
| } |
| |
| // |
| // built-in types |
| // |
| case 'v': |
| state.buffer.append("void"); break; |
| case 'w': |
| state.buffer.append("wchar_t"); break; |
| case 'b': |
| state.buffer.append("bool"); break; |
| case 'c': |
| state.buffer.append("char"); break; |
| case 'a': |
| state.buffer.append("signed char"); break; |
| case 'h': |
| state.buffer.append("unsigned char"); break; |
| case 's': |
| state.buffer.append("short"); break; |
| case 't': |
| state.buffer.append("unsigned short"); break; |
| case 'i': |
| state.buffer.append("int"); break; |
| case 'j': |
| state.buffer.append("unsigned int"); break; |
| case 'l': |
| state.buffer.append("long"); break; |
| case 'm': |
| state.buffer.append("unsigned long"); break; |
| case 'x': |
| state.buffer.append("long long"); break; |
| case 'y': |
| state.buffer.append("unsigned long long"); break; |
| case 'n': |
| state.buffer.append("__int128"); break; |
| case 'o': |
| state.buffer.append("unsigned __int128"); break; |
| case 'f': |
| state.buffer.append("float"); break; |
| case 'd': |
| state.buffer.append("double"); break; |
| case 'e': |
| state.buffer.append("long double"); break; |
| case 'g': |
| state.buffer.append("__float128"); break; |
| case 'z': |
| state.buffer.append("..."); break; |
| case 'D': { |
| ch = state.get(); |
| switch (ch) { |
| case 'd': |
| state.buffer.append("::std::decimal::decimal64"); break; |
| case 'e': |
| state.buffer.append("::std::decimal::decimal128"); break; |
| case 'f': |
| state.buffer.append("::std::decimal::decimal32"); break; |
| case 'h': |
| state.buffer.append("::std::decimal::binary16"); break; // TODO: a guess; what's the actual C++ name for the half-float? |
| case 'i': |
| state.buffer.append("char32_t"); break; |
| case 's': |
| state.buffer.append("char16_t"); break; |
| default: |
| // Dp, Dt, DT |
| state.unget(); throw state.notImplemented(); |
| } |
| } |
| case 'u': |
| state.buffer.append(unmangleName(state)); |
| return state.popAndRemember(SubstType.TYPE); |
| |
| // |
| // <class-enum-type> ::= <unqualified-name> | <nested-name> |
| // |
| case 'N': |
| state.unget(); |
| state.buffer.append(unmangleNestedName(state, false)); |
| state.remember(SubstType.TYPE); |
| break; |
| |
| case 'F': |
| // <function-type> ::= F [Y] <bare-function-type> E |
| if (state.peek() == 'Y') { |
| state.skip(); |
| state.buffer.append("extern \"C\" "); |
| } |
| state.buffer.append(unmangleBareFunctionType(state, true)); |
| state.consume('E'); |
| state.remember(SubstType.TYPE); |
| break; |
| |
| case 'M': { |
| state.unget(); |
| String name = unmanglePtm(state); |
| state.buffer.append(name); |
| state.remember(name, SubstType.TYPE); |
| break; |
| } |
| |
| case 'S': |
| state.unget(); |
| state.buffer.append(unmangleSubstitution(state)); |
| break; |
| |
| case 'T': |
| // either <template-param> or <template-template-param> <template-args> |
| state.unget(); |
| state.buffer.append(unmangleTemplateParam(state)); |
| if (state.peek() == 'I') { |
| state.buffer.append(unmangleTemplateArgs(state, false)); |
| } |
| break; |
| |
| case 'A': |
| state.unget(); |
| state.buffer.append(unmangleArrayType(state)); |
| break; |
| |
| default: |
| state.unget(); |
| String unqual = unmangleUnqualifiedName(state); |
| state.buffer.append(unqual); |
| if (state.peek() == 'I') { |
| // unscoped-template-name |
| state.remember(unqual, SubstType.TEMPLATE_PREFIX); |
| state.buffer.append(unmangleTemplateArgs(state, false)); |
| } |
| state.remember(SubstType.TYPE); |
| break; |
| } |
| return state.pop(); |
| } |
| |
| |
| /** |
| * Insert a "*" or "&" into a string. If this is a function type, |
| * insert in front of the argument list, not after. |
| * @param buffer |
| * @param string |
| */ |
| private void ptrOrRefize(StringBuilder buffer, String string) { |
| char last = buffer.length() > 0 ? buffer.charAt(buffer.length() - 1) : 0; |
| if (last == ')' || last == ']') { |
| char match = last == ')' ? '(' : '['; |
| int stack = 0; |
| int idx = buffer.length() - 1; |
| while (idx > 0) { |
| char ch = buffer.charAt(idx); |
| if (ch == last) |
| stack++; |
| else if (ch == match) { |
| stack--; |
| if (stack == 0) |
| break; |
| } |
| idx--; |
| } |
| buffer.insert(idx, '(' + string + ')'); |
| } else { |
| buffer.append(string); |
| } |
| } |
| |
| /* |
| <array-type> ::= A <positive dimension number> _ <element type> |
| ::= A [<dimension expression>] _ <element type> |
| |
| */ |
| private String unmangleArrayType(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| state.consume('A'); |
| |
| String count; |
| |
| char ch = state.peek(); |
| if (ch >= '0' && ch <= '9') { |
| int num = doUnmangleNonNegativeNumber(state); |
| count = "" + num; |
| } else { |
| throw state.notImplemented(); |
| } |
| state.consume('_'); |
| |
| state.buffer.append(unmangleType(state)); |
| |
| state.buffer.append('['); |
| state.buffer.append(count); |
| state.buffer.append(']'); |
| |
| return state.pop(); |
| } |
| |
| /* |
| <pointer-to-member-type> ::= M <class type> <member type> |
| */ |
| private String unmanglePtm(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| state.consume('M'); |
| String klass = unmangleType(state); |
| String ptrquals = unmangleCVQualifiers(state); |
| try { |
| state.safePush(); |
| state.buffer.append(doUnmangleFunctionWithName(state, true, '(' + klass + "::*)")); |
| state.safePop(); |
| } catch (UnmanglingException e) { |
| // may be pointer to member (field) |
| state.safeBacktrack(); |
| state.buffer.append(unmangleType(state)); |
| state.buffer.append(' '); |
| state.buffer.append(klass); |
| state.buffer.append("::*"); |
| } |
| if (ptrquals.length() > 0) { |
| state.buffer.append(' '); |
| state.buffer.append(ptrquals); |
| } |
| return state.pop(); |
| } |
| |
| /** |
| * Unmangle any sequence of CV quals |
| * @param state state |
| * @return String |
| */ |
| private String unmangleCVQualifiers(UnmangleState state) { |
| state.push(); |
| while (true) { |
| boolean matched = true; |
| switch (state.peek()) { |
| case 'r': |
| state.skip(); |
| if (state.hasCurrent()) state.buffer.append(' '); |
| state.buffer.append("restrict"); |
| break; |
| case 'V': |
| state.skip(); |
| if (state.hasCurrent()) state.buffer.append(' '); |
| state.buffer.append("volatile"); |
| break; |
| case 'K': |
| state.skip(); |
| if (state.hasCurrent()) state.buffer.append(' '); |
| state.buffer.append("const"); |
| break; |
| default: |
| matched = false; |
| break; |
| } |
| if (!matched) |
| break; |
| } |
| return state.pop(); |
| } |
| |
| static class Operator { |
| String name; |
| /** for unary or binary ops; other questionable ones are 0 */ |
| int numops; |
| |
| public Operator(String name, int numops) { |
| this.name = name; |
| this.numops = numops; |
| } |
| } |
| |
| static Map<String, Operator> operators = new HashMap<String, Operator>(); |
| |
| private static void registerOperator(String code, String name, int opcnt) { |
| if (operators.containsKey(code)) |
| throw new IllegalStateException(); |
| operators.put(code, new Operator(name, opcnt)); |
| } |
| |
| static { |
| registerOperator("nw", "new", 0); |
| registerOperator("na", "new[]", 0); |
| registerOperator("dl", "delete", 0); |
| registerOperator("da", "delete[]", 0); |
| registerOperator("ps", "+", 1); |
| registerOperator("ng", "-", 1); |
| registerOperator("ad", "&", 1); |
| registerOperator("de", "*", 1); |
| registerOperator("co", "~", 1); |
| registerOperator("pl", "+", 2); |
| registerOperator("mi", "-", 2); |
| registerOperator("ml", "*", 2); |
| registerOperator("dv", "/", 2); |
| registerOperator("rm", "%", 2); |
| registerOperator("an", "&", 2); |
| registerOperator("or", "|", 2); |
| registerOperator("eo", "^", 2); |
| registerOperator("aS", "=", 2); |
| registerOperator("pL", "+=", 2); |
| registerOperator("mI", "-=", 2); |
| registerOperator("mL", "*=", 2); |
| registerOperator("dV", "/=", 2); |
| registerOperator("rM", "%=", 2); |
| registerOperator("aN", "&=", 2); |
| registerOperator("oR", "|=", 2); |
| registerOperator("eO", "^=", 2); |
| registerOperator("ls", "<<", 2); |
| registerOperator("rs", ">>", 2); |
| registerOperator("lS", "<<=", 2); |
| registerOperator("rS", ">>=", 2); |
| registerOperator("eq", "==", 2); |
| registerOperator("ne", "!=", 2); |
| registerOperator("lt", "<", 2); |
| registerOperator("gt", ">", 2); |
| registerOperator("le", "<=", 2); |
| registerOperator("ge", ">=", 2); |
| registerOperator("nt", "!", 1); |
| registerOperator("aa", "&&", 2); |
| registerOperator("oo", "||", 2); |
| registerOperator("pp", "++", 1); |
| registerOperator("mm", "--", 1); |
| registerOperator("cm", ",", 2); |
| registerOperator("pm", "->*", 2); |
| registerOperator("pt", "->", 2); |
| registerOperator("cl", "()", 1); |
| registerOperator("ix", "[]", 2); |
| registerOperator("qu", "?", 3); |
| registerOperator("st", "sizeof ", 0); // type |
| registerOperator("sz", "sizeof", 1); // expression |
| registerOperator("at", "alignof ", 0); // type |
| registerOperator("az", "alignof", 1); // expression |
| registerOperator("cv", "()", 1); |
| } |
| |
| /* |
| <unqualified-name> ::= <operator-name> = lowercase |
| ::= <ctor-dtor-name> = C1-3 D0-2 ... |
| ::= <source-name> = <number> ... |
| ::= <unnamed-type-name> = Ut ... |
| |
| */ |
| private String unmangleUnqualifiedName(UnmangleState state) throws UnmanglingException { |
| char ch = state.peek(); |
| if (ch >= '0' && ch <= '9') { |
| return unmangleSourceName(state); |
| } |
| else if (ch >= 'a' && ch <= 'z') { |
| return unmangleOperatorName(state); |
| } |
| else if (ch == 'U') { |
| return unmangleUnnamedTypeName(state); |
| } |
| else if (ch == 'C') { |
| state.push(); |
| String last = simpleName(state.lastSubstitutedName()); |
| state.get(); |
| switch (state.get()) { |
| case '1': |
| case '2': |
| case '3': |
| state.buffer.append(last); |
| return state.pop(); |
| default: |
| state.unget(); |
| throw state.unexpected("constructor name"); |
| } |
| } |
| else if (ch == 'D') { |
| state.push(); |
| String last = simpleName(state.lastSubstitutedName()); |
| state.get(); |
| state.buffer.append('~'); |
| state.buffer.append(last); |
| switch (state.get()) { |
| case '0': |
| return state.pop(); |
| case '1': |
| return state.pop(); |
| case '2': |
| return state.pop(); |
| default: |
| state.unget(); |
| throw state.unexpected("destructor name"); |
| } |
| } |
| throw state.unexpected(); |
| } |
| |
| /** |
| * @param name |
| * @return |
| */ |
| private String simpleName(String name) { |
| int idx = name.lastIndexOf("::"); |
| if (idx >= 0) |
| return name.substring(idx + 2); |
| return name; |
| } |
| |
| /* |
| <unnamed-type-name> ::= Ut [ <nonnegative number> ] _ |
| <unnamed-type-name> ::= <closure-type-name> |
| |
| <closure-type-name> ::= Ul <lambda-sig> E [ <nonnegative number> ] _ |
| |
| */ |
| private String unmangleUnnamedTypeName(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| state.consume('U'); |
| switch (state.get()) { |
| case 't': |
| state.buffer.append("<unnamed #"); |
| if (state.peek() != '_') { |
| state.buffer.append("" + (doUnmangleNonNegativeNumber(state) + 2)); |
| } else { |
| state.buffer.append("1"); |
| } |
| state.buffer.append('>'); |
| state.consume('_'); |
| break; |
| case 'l': |
| throw state.notImplemented(); |
| default: |
| throw state.unexpected(); |
| } |
| return state.pop(); |
| } |
| |
| /* |
| */ |
| private String unmangleOperatorName(UnmangleState state) throws UnmanglingException { |
| state.push(); |
| char ch = state.get(); |
| String op = "" + ch; |
| if (ch == 'v') { |
| // vendor type <digit> <source-name> |
| ch = state.get(); |
| if (ch >= '0' && ch <= '9') { |
| int opcount = ch - '0'; |
| op = unmangleSourceName(state); |
| boolean first = true; |
| |
| // pretend it's a function, to differentiate |
| state.buffer.append('('); |
| while (opcount-- > 0) { |
| if (first) |
| first = false; |
| else |
| state.buffer.append(','); |
| } |
| state.buffer.append(')'); |
| } else { |
| throw state.unexpected(); |
| } |
| return state.pop(); |
| } |
| |
| ch = state.get(); |
| if (!Character.isLetter(ch)) { |
| throw state.unexpected(); |
| } |
| |
| op += ch; |
| |
| Operator oper = operators.get(op); |
| if (oper == null) { |
| throw state.unexpected(); |
| } |
| |
| state.buffer.append("operator "); |
| |
| // special cases |
| if (op.equals("cv")) { |
| state.buffer.append(unmangleType(state)); |
| // fall through |
| } |
| |
| state.buffer.append(oper.name); |
| return state.pop(); |
| |
| } |
| } |