blob: 9aebc1ea0c9d15e87e8e61c1abc0d4cecd5c7f6b [file] [log] [blame]
/*******************************************************************************
* 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> 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>();
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 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);
}
/*
<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();
}
}