#!/usr/bin/env python
#===- lib/sanitizer_common/scripts/gen_dynamic_list.py ---------------------===#
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
#===------------------------------------------------------------------------===#
#
# Generates the list of functions that should be exported from sanitizer
# runtimes. The output format is recognized by --dynamic-list linker option.
# Usage:
#   gen_dynamic_list.py libclang_rt.*san*.a [ files ... ]
#
#===------------------------------------------------------------------------===#
from __future__ import print_function
import argparse
import os
import re
import subprocess
import sys
import platform

new_delete = set([
                  '_Znam', '_ZnamRKSt9nothrow_t',    # operator new[](unsigned long)
                  '_Znwm', '_ZnwmRKSt9nothrow_t',    # operator new(unsigned long)
                  '_Znaj', '_ZnajRKSt9nothrow_t',    # operator new[](unsigned int)
                  '_Znwj', '_ZnwjRKSt9nothrow_t',    # operator new(unsigned int)
                  # operator new(unsigned long, std::align_val_t)
                  '_ZnwmSt11align_val_t', '_ZnwmSt11align_val_tRKSt9nothrow_t',
                  # operator new(unsigned int, std::align_val_t)
                  '_ZnwjSt11align_val_t', '_ZnwjSt11align_val_tRKSt9nothrow_t',
                  # operator new[](unsigned long, std::align_val_t)
                  '_ZnamSt11align_val_t', '_ZnamSt11align_val_tRKSt9nothrow_t',
                  # operator new[](unsigned int, std::align_val_t)
                  '_ZnajSt11align_val_t', '_ZnajSt11align_val_tRKSt9nothrow_t',
                  '_ZdaPv', '_ZdaPvRKSt9nothrow_t',  # operator delete[](void *)
                  '_ZdlPv', '_ZdlPvRKSt9nothrow_t',  # operator delete(void *)
                  '_ZdaPvm',                         # operator delete[](void*, unsigned long)
                  '_ZdlPvm',                         # operator delete(void*, unsigned long)
                  '_ZdaPvj',                         # operator delete[](void*, unsigned int)
                  '_ZdlPvj',                         # operator delete(void*, unsigned int)
                  # operator delete(void*, std::align_val_t)
                  '_ZdlPvSt11align_val_t', '_ZdlPvSt11align_val_tRKSt9nothrow_t',
                  # operator delete[](void*, std::align_val_t)
                  '_ZdaPvSt11align_val_t', '_ZdaPvSt11align_val_tRKSt9nothrow_t',
                  # operator delete(void*, unsigned long,  std::align_val_t)
                  '_ZdlPvmSt11align_val_t',
                  # operator delete[](void*, unsigned long, std::align_val_t)
                  '_ZdaPvmSt11align_val_t',
                  # operator delete(void*, unsigned int,  std::align_val_t)
                  '_ZdlPvjSt11align_val_t',
                  # operator delete[](void*, unsigned int, std::align_val_t)
                  '_ZdaPvjSt11align_val_t',
                  ])

versioned_functions = set(['memcpy', 'pthread_attr_getaffinity_np',
                           'pthread_cond_broadcast',
                           'pthread_cond_destroy', 'pthread_cond_init',
                           'pthread_cond_signal', 'pthread_cond_timedwait',
                           'pthread_cond_wait', 'realpath',
                           'sched_getaffinity'])

def get_global_functions(nm_executable, library):
  functions = []
  nm = os.environ.get('NM', nm_executable)
  nm_proc = subprocess.Popen([nm, library], stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
  nm_out = nm_proc.communicate()[0].decode().split('\n')
  if nm_proc.returncode != 0:
    raise subprocess.CalledProcessError(nm_proc.returncode, nm)
  func_symbols = ['T', 'W']
  # On PowerPC, nm prints function descriptors from .data section.
  if platform.uname()[4] in ["powerpc", "ppc64"]:
    func_symbols += ['D']
  for line in nm_out:
    cols = line.split(' ')
    if len(cols) == 3 and cols[1] in func_symbols :
      functions.append(cols[2])
  return functions

def main(argv):
  parser = argparse.ArgumentParser()
  parser.add_argument('--version-list', action='store_true')
  parser.add_argument('--extra', default=[], action='append')
  parser.add_argument('libraries', default=[], nargs='+')
  parser.add_argument('--nm-executable', required=True)
  parser.add_argument('-o', '--output', required=True)
  args = parser.parse_args()

  result = []

  all_functions = []
  for library in args.libraries:
    all_functions.extend(get_global_functions(args.nm_executable, library))
  function_set = set(all_functions)
  for func in all_functions:
    # Export new/delete operators.
    if func in new_delete:
      result.append(func)
      continue
    # Export interceptors.
    match = re.match('__interceptor_(.*)', func)
    if match:
      result.append(func)
      # We have to avoid exporting the interceptors for versioned library
      # functions due to gold internal error.
      orig_name = match.group(1)
      if orig_name in function_set and (args.version_list or orig_name not in versioned_functions):
        result.append(orig_name)
      continue
    # Export sanitizer interface functions.
    if re.match('__sanitizer_(.*)', func):
      result.append(func)

  # Additional exported functions from files.
  for fname in args.extra:
    f = open(fname, 'r')
    for line in f:
      result.append(line.rstrip())
  # Print the resulting list in the format recognized by ld.
  with open(args.output, 'w') as f:
    print('{', file=f)
    if args.version_list:
      print('global:', file=f)
    result.sort()
    for sym in result:
      print(u'  %s;' % sym, file=f)
    if args.version_list:
      print('local:', file=f)
      print('  *;', file=f)
    print('};', file=f)

if __name__ == '__main__':
  main(sys.argv)
