GP-5101 - MDMang - Properly process anon NS in vxtables owner and parentage; start work on OutputOptions, as needed for anon NS

This commit is contained in:
ghizard 2024-11-07 13:48:38 -05:00
parent 710e2a8ba3
commit 9c942026b5
20 changed files with 315 additions and 69 deletions

View File

@ -66,7 +66,13 @@ public class MicrosoftDemangler implements Demangler {
demangler.setIsFunction(mContext.shouldInterpretAsFunction());
try {
item = demangler.demangle();
object = MicrosoftDemanglerUtil.convertToDemangledObject(item, mangled);
if (item == null) {
return null;
}
String originalDemangled = item.toString();
demangler.getOutputOptions().setUseEncodedAnonymousNamespace(true);
object =
MicrosoftDemanglerUtil.convertToDemangledObject(item, mangled, originalDemangled);
if (object != null) {
object.setMangledContext(context);
}
@ -103,7 +109,13 @@ public class MicrosoftDemangler implements Demangler {
demangler.setIsFunction(mContext.shouldInterpretAsFunction());
try {
mdType = demangler.demangleType();
dataType = MicrosoftDemanglerUtil.convertToDemangledDataType(mdType, mangled);
if (mdType == null) {
return null;
}
String originalDemangled = item.toString();
demangler.getOutputOptions().setUseEncodedAnonymousNamespace(true);
dataType = MicrosoftDemanglerUtil.convertToDemangledDataType(mdType, mangled,
originalDemangled);
if (dataType != null) {
dataType.setMangledContext(context);
}

View File

@ -50,22 +50,27 @@ public class MicrosoftDemanglerUtil {
* is not appropriate for {@link MDDataType} and some other types of {@link MDParsableItem}
* @param item the item to convert
* @param mangled the original mangled string
* @param originalDemangled the original demangled string
* @return the {@link DemangledObject} result
* @throws DemangledException up issue converting to a {@link DemangledObject}
*/
static DemangledObject convertToDemangledObject(MDParsableItem item, String mangled)
static DemangledObject convertToDemangledObject(MDParsableItem item, String mangled,
String originalDemangled)
throws DemangledException {
return processItem(item, mangled, item.toString());
return processItem(item, mangled, originalDemangled);
}
/**
* Method to convert an {@link MDDataType} into a {@link DemangledDataType}
* Method to convert an {@link MDDataType} into a {@link DemangledDataType}. Demangler
* needs to have already run to process the type before calling this method
* @param type the type to convert
* @param mangled the original mangled string
* @param mangled the mangled string
* @param originalDemangled the original demangled string
* @return the result
*/
static DemangledDataType convertToDemangledDataType(MDDataType type, String mangled) {
return processDataType(null, type, mangled, type.toString());
static DemangledDataType convertToDemangledDataType(MDDataType type, String mangled,
String originalDemangled) {
return processDataType(null, type, mangled, originalDemangled);
}
//==============================================================================================
@ -121,14 +126,8 @@ public class MicrosoftDemanglerUtil {
}
}
else if (qual.isAnon()) {
// Instead of using the standard qual.toString() method, which returns
// "`anonymous namespace'" for anonymous qualifiers, we use qual.getAnonymousName()
// which will have the underlying anonymous name of the form "A0xfedcba98" to create
// a standardized anonymous name that is distinguishable from other anonymous names.
// The standardized name comes from createStandardAnonymousNamespaceNode(). This
// is especially important when there are sibling anonymous names.
String anon = MDMangUtils.createStandardAnonymousNamespaceNode(qual.getAnonymousName());
demangled = new DemangledNamespaceNode(mangled, qual.toString(), anon);
String orig = qual.getAnonymousName();
demangled = new DemangledNamespaceNode(mangled, orig, qual.toString());
}
else if (qual.isInterface()) {
// TODO: need to do better; setting namespace for now

View File

@ -178,6 +178,29 @@ public class MicrosoftDemanglerExtraTest extends AbstractGenericTest {
//==============================================================================================
@Test
public void testVxTableAnonymousNsInOwnerAndBackref() throws Exception {
String mangled = "??_7a@?A0xfedcba98@b@@6B012@01@@";
String mTruth =
"const b::`anonymous namespace'::a::`vftable'{for `b::A0xfedcba98::a's `A0xfedcba98::a'}";
String gTruth =
"const b::_anon_FEDCBA98::a::`vftable'{for `b::_anon_FEDCBA98::a's `_anon_FEDCBA98::a'}";
MicrosoftDemangler demangler = new MicrosoftDemangler();
MicrosoftMangledContext context =
demangler.createMangledContext(mangled, null, program32, address32);
DemangledObject obj = demangler.demangle(context);
String originalDemangled = obj.getOriginalDemangled();
assertEquals(mTruth, originalDemangled);
String demangled = demangler.getMdItem().toString();
assertEquals(gTruth, demangled);
}
//==============================================================================================
@Test
//This test checks that we can provide a mangled string for a function namespace.
// The return String from getOriginalMangled() is not null only for this special

View File

@ -40,6 +40,8 @@ import mdemangler.template.MDTemplateArgumentsList;
public class MDMang {
public static final char DONE = MDCharacterIterator.DONE;
private MDOutputOptions outputOptions = new MDOutputOptions();
protected int architectureSize = 32;
protected boolean isFunction = false;
@ -110,7 +112,13 @@ public class MDMang {
}
//==============================================================================================
// Control
// Output Options
public MDOutputOptions getOutputOptions() {
return outputOptions;
}
//==============================================================================================
// Demangling options
/**
* Controls whether an exception is thrown if there are remaining characters after demangling.

View File

@ -0,0 +1,51 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package mdemangler;
/**
* Options for controlling demangler output. Quick stub for now. Full implementation was planned
* for another ticket
*/
public class MDOutputOptions {
private boolean useEncodedAnonymousNamespaceNumber;
/**
* Constructor
*/
public MDOutputOptions() {
}
/**
* Sets the option for whether to use or not use the numerical encoding to craft a
* unique anonymous namespace. The default {@code false} is to produce the normal anonymous
* namespace string produced by Microsoft's {@code undname}
* @param useEncodedNumber {@code true} to produce a namespace that uses the encoded number
*/
public void setUseEncodedAnonymousNamespace(boolean useEncodedNumber) {
this.useEncodedAnonymousNamespaceNumber = useEncodedNumber;
}
/**
* Returns {@code true} if the demangler will use the encoded number in creating the
* anonymous namespace component
* @return {@code true} if the flag is set
*/
public boolean useEncodedAnonymousNamespace() {
return useEncodedAnonymousNamespaceNumber;
}
}

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -30,13 +30,13 @@ public class MDBasicName extends MDParsableItem {
MDReusableName reusableName;
MDObjectCPP embeddedObject;
MDQualification embeddedObjectQualification;
String nameModifier;
MDNameModifier nameModifier;
public MDBasicName(MDMang dmang) {
super(dmang);
}
public void setNameModifier(String nameModifier) {
public void setNameModifier(MDNameModifier nameModifier) {
this.nameModifier = nameModifier;
}
@ -165,7 +165,7 @@ public class MDBasicName extends MDParsableItem {
templateNameAndArguments.insert(builder);
}
if (nameModifier != null) {
builder.append(nameModifier);
builder.append(nameModifier.getModifier());
}
}

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -34,7 +34,12 @@ public class MDFragmentName extends MDParsableItem {
@Override
public void insert(StringBuilder builder) {
dmang.insertString(builder, name);
if (dmang.getOutputOptions().useEncodedAnonymousNamespace()) {
dmang.insertString(builder, MDMangUtils.createStandardAnonymousNamespaceNode(name));
}
else {
dmang.insertString(builder, name);
}
}
public void setName(String name) {

View File

@ -0,0 +1,29 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package mdemangler.naming;
/**
* This interface is for items that provide a name modifier
*/
public interface MDNameModifier {
/**
* Returns the modifier string for the name
* @return the modifier
*/
public String getModifier();
}

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -93,7 +93,7 @@ public class MDQualifiedBasicName extends MDParsableItem {
return qualification;
}
public void setNameModifier(String nameModifier) {
public void setNameModifier(MDNameModifier nameModifier) {
basicName.setNameModifier(nameModifier);
}

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -91,7 +91,13 @@ public class MDQualifier extends MDParsableItem {
name.insert(builder);
}
else if (nameAnonymous != null) {
dmang.insertString(builder, ANONYMOUS_NAMESPACE);
if (dmang.getOutputOptions().useEncodedAnonymousNamespace()) {
dmang.insertString(builder,
MDMangUtils.createStandardAnonymousNamespaceNode(nameAnonymous.getName()));
}
else {
dmang.insertString(builder, ANONYMOUS_NAMESPACE);
}
}
else if (nameInterface != null) {
nameInterface.insert(builder);

View File

@ -148,9 +148,7 @@ public class MDObjectCPP extends MDObject {
typeInfo.setTypeCast();
}
typeInfo.parse();
if (!typeInfo.getNameModifier().isEmpty()) {
qualifiedName.setNameModifier(typeInfo.getNameModifier());
}
qualifiedName.setNameModifier(typeInfo);
if (qualifiedName.isTypeCast()) {
applyFunctionReturnTypeToTypeCastOperatorName();
}

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -23,11 +23,19 @@ import mdemangler.*;
*/
public class MDGuard extends MDTypeInfo {
private MDEncodedNumber guardNumber;
public MDGuard(MDMang dmang) {
super(dmang);
guardNumber = new MDEncodedNumber(dmang);
mdtype = new MDType(dmang);
}
@Override
public String getModifier() {
return "{" + guardNumber + "}'";
}
@Override
public void insert(StringBuilder builder) {
super.insert(builder);
@ -35,9 +43,7 @@ public class MDGuard extends MDTypeInfo {
@Override
protected void parseInternal() throws MDException {
MDEncodedNumber guardNumber = new MDEncodedNumber(dmang);
guardNumber.parse();
nameModifier = "{" + guardNumber + "}'";
super.parseInternal();
}
}

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,11 +16,12 @@
package mdemangler.typeinfo;
import mdemangler.*;
import mdemangler.naming.MDNameModifier;
/**
*
*/
public class MDTypeInfo extends MDParsableItem {
public class MDTypeInfo extends MDParsableItem implements MDNameModifier {
private static final String PRIVATE = "private: ";
private static final String PROTECTED = "protected: ";
private static final String PUBLIC = "public: ";
@ -61,14 +62,13 @@ public class MDTypeInfo extends MDParsableItem {
protected MDType mdtype;
protected boolean isTypeCast;
protected String nameModifier = "";
public MDTypeInfo(MDMang dmang) {
super(dmang, 1);
}
public String getNameModifier() {
return nameModifier;
@Override
public String getModifier() {
return "";
}
public void setPrivate() {

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -57,6 +57,12 @@ public class MDVCall extends MDMemberFunctionInfo {
callIndex = new MDEncodedNumber(dmang);
}
@Override
public String getModifier() {
// TODO: Future specialization on 16-bit or 32plus
return getNameModifier_32PlusBitModel();
}
@Override
public void insert(StringBuilder builder) {
// TODO: Future specialization on 16-bit or 32plus
@ -197,8 +203,6 @@ public class MDVCall extends MDMemberFunctionInfo {
}
// TODO evaluate whether parseInternal() or parse.
super.parseInternal();
// TODO: Future specialization on 16-bit or 32plus
nameModifier = getNameModifier_32PlusBitModel();
}
}

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -24,21 +24,27 @@ import mdemangler.functiontype.MDFunctionType;
*/
public class MDVFAdjustor extends MDMemberFunctionInfo {
private MDEncodedNumber adjustment;
// Comment from wiki page: This kind of thunk (G,H,O,P,W,X) function is always virtual, and
// used to represent the logical "this" adjustor property, which means an offset to the
// true "this" value in some multiple inheritance situations.
public MDVFAdjustor(MDMang dmang) {
super(dmang);
mdtype = new MDFunctionType(dmang);
adjustment = new MDEncodedNumber(dmang);
setVirtual();
setThunk();
}
@Override
public String getModifier() {
return "`adjustor{" + adjustment + "}' ";
}
@Override
protected void parseInternal() throws MDException {
MDEncodedNumber adjustment = new MDEncodedNumber(dmang);
adjustment.parse();
nameModifier = "`adjustor{" + adjustment + "}' ";
super.parseInternal(); // TODO evaluate whether parseInternal() or parse.
}
}

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -24,22 +24,31 @@ import mdemangler.functiontype.MDFunctionType;
*/
public class MDVtordisp extends MDMemberFunctionInfo {
// 20200507: Believe this to be <offset-to-vtordisp>
private MDEncodedNumber vtorDisplacement;
// 20200507: Believe this to be <static-offset>
private MDEncodedNumber adjustment;
public MDVtordisp(MDMang dmang) {
super(dmang);
mdtype = new MDFunctionType(dmang);
vtorDisplacement = new MDEncodedNumber(dmang);
adjustment = new MDEncodedNumber(dmang);
setVirtual();
setThunk();
}
@Override
public String getModifier() {
return "`vtordisp{" + vtorDisplacement + "," + adjustment + "}' ";
}
@Override
protected void parseInternal() throws MDException {
// 20200507: Believe this to be <offset-to-vtordisp>
MDEncodedNumber vtorDisplacement = new MDEncodedNumber(dmang);
vtorDisplacement.parse();
// 20200507: Believe this to be <static-offset>
MDEncodedNumber adjustment = new MDEncodedNumber(dmang);
adjustment.parse();
nameModifier = "`vtordisp{" + vtorDisplacement + "," + adjustment + "}' ";
super.parseInternal();
}
}

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -24,6 +24,15 @@ import mdemangler.functiontype.MDFunctionType;
*/
public class MDVtordispex extends MDMemberFunctionInfo {
// 20200507: Believe this to be <offset-to-vptr>
private MDEncodedNumber a = new MDEncodedNumber(dmang);
// 20200507: Believe this to be <vbase-offset-offset>
private MDEncodedNumber b = new MDEncodedNumber(dmang);
// 20200507: Believe this to be <offset-to-vtordisp>
private MDEncodedNumber c = new MDEncodedNumber(dmang);
// 20200507: Believe this to be <static-offset>
private MDEncodedNumber d = new MDEncodedNumber(dmang);
public MDVtordispex(MDMang dmang) {
super(dmang);
mdtype = new MDFunctionType(dmang);
@ -31,21 +40,21 @@ public class MDVtordispex extends MDMemberFunctionInfo {
setThunk();
}
@Override
public String getModifier() {
return "`vtordispex{" + a + "," + b + "," + c + "," + d + "}' ";
}
@Override
protected void parseInternal() throws MDException {
// 20200507: Believe this to be <offset-to-vptr>
MDEncodedNumber a = new MDEncodedNumber(dmang);
a.parse();
// 20200507: Believe this to be <vbase-offset-offset>
MDEncodedNumber b = new MDEncodedNumber(dmang);
b.parse();
// 20200507: Believe this to be <offset-to-vtordisp>
MDEncodedNumber c = new MDEncodedNumber(dmang);
c.parse();
// 20200507: Believe this to be <static-offset>
MDEncodedNumber d = new MDEncodedNumber(dmang);
d.parse();
nameModifier = "`vtordispex{" + a + "," + b + "," + c + "," + d + "}' ";
super.parseInternal();
}
}

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -45,6 +45,11 @@ public class MDVxTable extends MDTypeInfo {
goodNestedTermination = false;
}
@Override
public String getModifier() {
return generateNameModifier();
}
public MDCVMod getCVMod() {
return cvmod;
}
@ -94,10 +99,9 @@ public class MDVxTable extends MDTypeInfo {
dmang.increment();
goodNestedTermination = true;
}
nameModifier = generateNameModifier();
}
String generateNameModifier() {
private String generateNameModifier() {
if (!goodNestedTermination) {
return "{for ??}";
}

View File

@ -4664,6 +4664,25 @@ public class MDMangBaseTest extends AbstractGenericTest {
demangleAndTest();
}
// vbtable with anonymous namespace
@Test
public void testUnderscore7AnonNs() throws Exception {
mangled = "??_7a@?A0xfedcba98@b@@6B@";
msTruth = "const b::`anonymous namespace'::a::`vftable'";
mdTruth = msTruth;
demangleAndTest();
}
// vbtable with anonymous namespace
@Test
public void testUnderscore7AnonNsAndBackref() throws Exception {
mangled = "??_7a@?A0xfedcba98@b@@6B012@01@@";
msTruth =
"const b::`anonymous namespace'::a::`vftable'{for `b::A0xfedcba98::a's `A0xfedcba98::a'}";
mdTruth = msTruth;
demangleAndTest();
}
@Test
public void testSpecialNames_R() throws Exception {
mangled = "??_R0X@8";

View File

@ -78,6 +78,64 @@ public class MDMangExtraTest extends AbstractGenericTest {
assertEquals("b::a", qualifications.get(0).toString());
}
// Anonymous namespace in owner and backref
@Test
public void testVxTableAnonymousNsInOwner() throws Exception {
String mangled = "??_7a@?A0xfedcba98@b@@6B@";
String truth = "const b::`anonymous namespace'::a::`vftable'";
String truth2 = "const b::_anon_FEDCBA98::a::`vftable'";
MDMangGhidra demangler = new MDMangGhidra();
demangler.setMangledSymbol(mangled);
demangler.setErrorOnRemainingChars(true);
demangler.setDemangleOnlyKnownPatterns(true);
MDParsableItem item = demangler.demangle();
MDObjectCPP cppItem = (MDObjectCPP) item;
MDVxTable vxTable = (MDVxTable) cppItem.getTypeInfo();
List<MDQualification> qualifications = vxTable.getNestedQualifications();
assertEquals(0, qualifications.size());
String demangled = item.toString();
assertEquals(truth, demangled);
demangler.getOutputOptions().setUseEncodedAnonymousNamespace(true);
demangled = item.toString();
assertEquals(truth2, demangled);
}
// Anonymous namespace in owner and backref
@Test
public void testVxTableAnonymousNsInOwnerAndBackref() throws Exception {
String mangled = "??_7a@?A0xfedcba98@b@@6B012@01@@";
String truth =
"const b::`anonymous namespace'::a::`vftable'{for `b::A0xfedcba98::a's `A0xfedcba98::a'}";
String truth2 =
"const b::_anon_FEDCBA98::a::`vftable'{for `b::_anon_FEDCBA98::a's `_anon_FEDCBA98::a'}";
MDMangGhidra demangler = new MDMangGhidra();
demangler.setMangledSymbol(mangled);
demangler.setErrorOnRemainingChars(true);
demangler.setDemangleOnlyKnownPatterns(true);
MDParsableItem item = demangler.demangle();
MDObjectCPP cppItem = (MDObjectCPP) item;
MDVxTable vxTable = (MDVxTable) cppItem.getTypeInfo();
List<MDQualification> qualifications = vxTable.getNestedQualifications();
assertEquals(2, qualifications.size());
String demangled = item.toString();
assertEquals(truth, demangled);
assertEquals("b::A0xfedcba98::a", qualifications.get(0).toString());
assertEquals("A0xfedcba98::a", qualifications.get(1).toString());
demangler.getOutputOptions().setUseEncodedAnonymousNamespace(true);
demangled = item.toString();
assertEquals(truth2, demangled);
assertEquals("b::_anon_FEDCBA98::a", qualifications.get(0).toString());
assertEquals("_anon_FEDCBA98::a", qualifications.get(1).toString());
}
// Need to test the demangleType() method to make sure it does the retry with LLVM mode
@Test
public void testDemangleTypeWithRetry() throws Exception {