blob: 39aa53b7e594732166780143bbf0e374da6cce19 [file] [log] [blame]
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Utilities for demangling C++ symbols."""
import collections
import itertools
import logging
import re
import subprocess
import path_util
_LOWER_HEX_PATTERN = re.compile(r'^[0-9a-f]*$')
_PROMOTED_GLOBAL_NAME_DEMANGLED_PATTERN = re.compile(
r' \((\.\d+)?\.llvm\.\d+\)$')
_PROMOTED_GLOBAL_NAME_RAW_PATTERN = re.compile(r'(\.\d+)?\.llvm\.\d+$')
def StripLlvmPromotedGlobalNames(name):
"""Strips LLVM promoted global names suffix, and returns the result.
LLVM can promote global names by adding the suffix '.llvm.1234', or
'.1.llvm.1234', where the last numeric suffix is a hash. If demangle is
sucessful, the suffix transforms into, e.g., ' (.llvm.1234)' or
' (.1.llvm.1234)'. Otherwise the suffix is left as is. This function strips
the suffix to prevent it from intefering with name comparison.
"""
llvm_pos = name.find('.llvm.')
if llvm_pos < 0:
return name # Handles most cases.
if name.endswith(')'):
return _PROMOTED_GLOBAL_NAME_DEMANGLED_PATTERN.sub('', name)
return _PROMOTED_GLOBAL_NAME_RAW_PATTERN.sub('', name)
def _CanDemangle(name):
return name.startswith('_Z') or name.startswith('.Lswitch.table._Z')
def _ExtractDemanglablePart(names):
"""For each name in |names|, yields the part that can be demangled."""
def _IsLowerHex(s):
return _LOWER_HEX_PATTERN.match(s) is not None
for name in names:
# Strip prefixes before '_Z', e.g., '.Lswitch.table.'.
pos = name.find('_Z')
if pos > 0:
name = name[pos:]
# Some mangled symbols end with '$' followed by 32 lower-case hex digits,
# and possibly '.cfi'. These interfere with demangling by cxxfilt, and
# should be stripped.
if name.endswith('.cfi'):
name = name[:-4]
if len(name) > 33 and name[-33] == '$' and _IsLowerHex(name[-32:]):
yield name[:-33]
else:
yield name
def _PostProcessDemangledSymbol(old_name, new_name):
new_name = StripLlvmPromotedGlobalNames(new_name)
if old_name.startswith('.Lswitch.table.'):
new_name = 'Switch table for ' + new_name # Becomes ... [Switch table].
return new_name
def _DemangleNames(names):
"""Uses cxxfilt to demangle a list of names."""
# pylint: disable=unexpected-keyword-arg
proc = subprocess.Popen([path_util.GetCppFiltPath()],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
encoding='utf-8')
in_data = '\n'.join(_ExtractDemanglablePart(names))
stdout = proc.communicate(in_data)[0]
assert proc.returncode == 0
ret = [
_PostProcessDemangledSymbol(old_name, new_name)
for (old_name, new_name) in zip(names, stdout.splitlines())
]
if logging.getLogger().isEnabledFor(logging.INFO):
fail_count = sum(1 for s in ret if _CanDemangle(s))
if fail_count:
logging.info('* Failed to demangle %d/%d items', fail_count, len(ret))
return ret
def DemangleRemainingSymbols(raw_symbols):
"""Demangles any symbols that need it."""
to_process = [s for s in raw_symbols if _CanDemangle(s.full_name)]
if not to_process:
return
logging.info('Demangling %d symbols', len(to_process))
names = _DemangleNames([s.full_name for s in to_process])
for i, name in enumerate(names):
to_process[i].full_name = name
def DemangleSetsInDictsInPlace(key_to_names):
"""Demangles values as sets.
|key_to_names| is a dict from key to sets (or lists) of mangled names.
"""
all_names = []
for names in key_to_names.values():
all_names.extend(n for n in names if _CanDemangle(n))
if not all_names:
return key_to_names
logging.info('Demangling %d values', len(all_names))
it = iter(_DemangleNames(all_names))
for key, names in key_to_names.items():
key_to_names[key] = set(next(it) if _CanDemangle(n) else n for n in names)
assert(next(it, None) is None)
return None
def DemangleKeysAndMergeLists(name_to_list):
"""Demangles keys of a dict of lists, and returns the result.
Keys may demangle to a common name. When this happens, the corresponding lists
are merged in arbitrary order.
"""
keys = [key for key in name_to_list if _CanDemangle(key)]
if not keys:
return name_to_list
logging.info('Demangling %d keys', len(keys))
key_iter = iter(_DemangleNames(keys))
ret = collections.defaultdict(list)
for key, val in name_to_list.items():
ret[next(key_iter) if _CanDemangle(key) else key] += val
assert(next(key_iter, None) is None)
logging.info('* %d keys become %d keys' % (len(name_to_list), len(ret)))
return ret