#!/usr/bin/python
#
# Copyright (C) 2018 The Android Open Source Project
#
# 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.
#
"""Use addr2line to interpret tombstone contents

The defaults should work if this is run after running lunch.
"""

from __future__ import print_function
import argparse
import collections
import functools
import multiprocessing
import os
import re
import subprocess
import sys


# Patterns of things that we might want to match.

patterns = [
    (re.compile('(.* pc )([0-9a-f]+) +([^ ]+) .*'), 1, 3, 2),
    (re.compile('(.*)#[0-9]+ 0x[0-9a-f]+ +\((.*)\+0x([0-9a-f]+)\)'), 1, 2, 3)]


LookupInfo = collections.namedtuple('LookupInfo',
                                    ['line_number', 'details', 'file_name'])


def lookup_addr(args, object_path, address):
  try:
    if object_path[0] == os.path.sep:
      object_path = object_path[1:]
    parms = [args.addr2line, '-e',
             os.path.join(args.symbols, object_path), address]
    details = subprocess.check_output(parms).strip().split(':')
    return LookupInfo(
        line_number=details[-1],
        details=details,
        file_name=':'.join(details[:-1]))
  except subprocess.CalledProcessError:
    return None


def simple_match(line, info, indent, out_file):
  print('{} // From {}:{}'.format(
      line, info.file_name, info.line_number), file=out_file)


def source_match(line, info, indent, out_file):
  source = ''
  try:
    with open(info.file_name, 'r') as f:
      for i in range(int(info.line_number.split(' ')[0])):
        source = f.readline()
  # Fall back to the simple formatter on any error
  except Exception:
    simple_match(line, info, indent, out_file)
    return
  print(line, file=out_file)
  print('{}// From {}:{}'.format(
      ' ' * indent, info.file_name, info.line_number), file=out_file)
  print('{}  {}'.format(' ' * indent, ' '.join(source.strip().split())),
        file=out_file)


def process(in_file, out_file, args):
  for line in in_file:
    line = line.rstrip()
    groups = None
    for p in patterns:
      groups = p[0].match(line)
      if groups:
        break
    info = None
    if groups is not None:
      info = lookup_addr(args, groups.group(p[2]), groups.group(p[3]))
    if info is None:
      print(line, file=out_file)
      continue
    if args.source:
      source_match(line, info, len(groups.group(p[1])), out_file)
    else:
      simple_match(line, info, len(groups.group(p[1])), out_file)


def process_file(path, args):
    with open(path + args.suffix, 'w') as out_file:
      with open(path, 'r') as in_file:
        process(in_file, out_file, args)


def common_arg_parser():
  parser = argparse.ArgumentParser(description=
                                   'Add line information to a tombstone')
  parser.add_argument('--addr2line', type=str,
                      help='Path to addr2line',
                      default=os.path.join(
                          os.environ.get('ANDROID_TOOLCHAIN', ''),
                          'x86_64-linux-android-addr2line'))
  parser.add_argument('files', metavar='FILE', type=str, nargs='+',
                      help='a list of files to process')
  parser.add_argument('--jobs', type=int, default=32,
                      help='Number of parallel jobs to run')
  parser.add_argument('--source', default=False, action='store_true',
                      help='Attempt to print the source')
  parser.add_argument('--suffix', type=str, default='.txt',
                      help='Suffix to add to the processed file')
  return parser



def process_all(args):
  multiprocessing.Pool(32).map(functools.partial(process_file, args=args),
                               args.files)


if __name__ == '__main__':
  parser = common_arg_parser()
  parser.add_argument('--symbols', type=str,
                      help='Path to the symbols',
                      default=os.path.join(
                          os.environ.get('ANDROID_PRODUCT_OUT', ''), 'symbols'))
  process_all(parser.parse_args())