| # 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 |