blob: 40ba4879181193c45dfbbfa67e614a844c186c3f [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 logging
import re
import subprocess
import path_util
_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 _DemangleNames(names, tool_prefix):
"""Uses c++filt to demangle a list of names."""
proc = subprocess.Popen([path_util.GetCppFiltPath(tool_prefix)],
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
stdout = proc.communicate('\n'.join(names))[0]
assert proc.returncode == 0
ret = [StripLlvmPromotedGlobalNames(line) for line in stdout.splitlines()]
if logging.getLogger().isEnabledFor(logging.INFO):
fail_count = sum(1 for s in ret if s.startswith('_Z'))
if fail_count:
logging.info('* Failed to demangle %d/%d items', fail_count, len(ret))
return ret
def DemangleRemainingSymbols(raw_symbols, tool_prefix):
"""Demangles any symbols that need it."""
to_process = [s for s in raw_symbols if s.full_name.startswith('_Z')]
if not to_process:
return
logging.info('Demangling %d symbols', len(to_process))
names = _DemangleNames((s.full_name for s in to_process), tool_prefix)
for i, name in enumerate(names):
to_process[i].full_name = name
def DemangleSetsInDicts(key_to_names, tool_prefix):
"""Demangles values as sets, and returns the result.
|key_to_names| is a dict from key to sets (or lists) of mangled names.
"""
all_names = []
for names in key_to_names.itervalues():
all_names.extend(n for n in names if n.startswith('_Z'))
if not all_names:
return key_to_names
logging.info('Demangling %d values', len(all_names))
it = iter(_DemangleNames(all_names, tool_prefix))
ret = {}
for key, names in key_to_names.iteritems():
ret[key] = set(next(it) if n.startswith('_Z') else n for n in names)
assert(next(it, None) is None)
return ret
def DemangleKeysAndMergeLists(name_to_list, tool_prefix):
"""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 key.startswith('_Z')]
if not keys:
return name_to_list
logging.info('Demangling %d keys', len(keys))
key_iter = iter(_DemangleNames(keys, tool_prefix))
ret = collections.defaultdict(list)
for key, val in name_to_list.iteritems():
ret[next(key_iter) if key.startswith('_Z') 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