#!/usr/bin/env python
# Copyright (c) 2012 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.

"""Creates windows and posix stub files for a given set of signatures.

For libraries that need to be loaded outside of the standard executable startup
path mechanism, stub files need to be generated for the wanted functions.  In
windows, this is done via "def" files and the delay load mechanism.  On a posix
system, a set of stub functions need to be generated that dispatch to functions
found via dlsym.

This script takes a set of files, where each file is a list of C-style
signatures (one signature per line).  The output is either a windows def file,
or a header + implementation file of stubs suitable for use in a posix system.

This script also handles varidiac functions, e.g.
void printf(const char* s, ...);

TODO(hclam): Fix the situation for varidiac functions.
Stub for the above function will be generated and inside the stub function it
is translated to:
void printf(const char* s, ...) {
  printf_ptr(s, (void*)arg1);
}

Only one argument from the varidiac arguments is used and it will be used as
type void*.
"""

__author__ = 'ajwong@chromium.org (Albert J. Wong)'

import optparse
import os
import re
import string
import subprocess
import sys


class Error(Exception):
  pass


class BadSignatureError(Error):
  pass


class SubprocessError(Error):
  def __init__(self, message, error_code):
    Error.__init__(self)
    self.message = message
    self.error_code = error_code

  def __str__(self):
    return 'Failed with code %s: %s' % (self.message, repr(self.error_code))


# Regular expression used to parse function signatures in the input files.
# The regex is built around identifying the "identifier" for the function name.
# We consider the identifier to be the string that follows these constraints:
#
#   1) Starts with [_a-ZA-Z] (C++ spec 2.10).
#   2) Continues with [_a-ZA-Z0-9] (C++ spec 2.10).
#   3) Preceeds an opening parenthesis by 0 or more whitespace chars.
#
# From that, all preceeding characters are considered the return value.
# Trailing characters should have a substring matching the form (.*).  That
# is considered the arguments.
SIGNATURE_REGEX = re.compile('(?P<return_type>.+?)'
                             '(?P<name>[_a-zA-Z][_a-zA-Z0-9]+)\s*'
                             '\((?P<params>.*?)\)')

# Used for generating C++ identifiers.
INVALID_C_IDENT_CHARS = re.compile('[^_a-zA-Z0-9]')

# Constants defning the supported file types options.
FILE_TYPE_WIN_X86 = 'windows_lib'
FILE_TYPE_WIN_X64 = 'windows_lib_x64'
FILE_TYPE_POSIX_STUB = 'posix_stubs'
FILE_TYPE_WIN_DEF = 'windows_def'

# Template for generating a stub function definition.  Includes a forward
# declaration marking the symbol as weak.  This template takes the following
# named parameters.
#   return_type: The return type.
#   name: The name of the function.
#   params: The parameters to the function.
#   return_prefix: 'return ' if this function is not void. '' otherwise.
#   arg_list: The arguments used to call the stub function.
STUB_FUNCTION_DEFINITION = (
    """extern %(return_type)s %(name)s(%(params)s) __attribute__((weak));
%(return_type)s %(name)s(%(params)s) {
  %(return_prefix)s%(name)s_ptr(%(arg_list)s);
}""")

# Template for generating a variadic stub function definition with return
# value.
# Includes a forward declaration marking the symbol as weak.
# This template takes the following named parameters.
#   return_type: The return type.
#   name: The name of the function.
#   params: The parameters to the function.
#   arg_list: The arguments used to call the stub function without the
#             variadic argument.
#   last_named_arg: Name of the last named argument before the variadic
#                   argument.
VARIADIC_STUB_FUNCTION_DEFINITION = (
    """extern %(return_type)s %(name)s(%(params)s) __attribute__((weak));
%(return_type)s %(name)s(%(params)s) {
  va_list args___;
  va_start(args___, %(last_named_arg)s);
  %(return_type)s ret___ = %(name)s_ptr(%(arg_list)s, va_arg(args___, void*));
  va_end(args___);
  return ret___;
}""")

# Template for generating a variadic stub function definition without
# return value.
# Includes a forward declaration marking the symbol as weak.
# This template takes the following named parameters.
#   name: The name of the function.
#   params: The parameters to the function.
#   arg_list: The arguments used to call the stub function without the
#             variadic argument.
#   last_named_arg: Name of the last named argument before the variadic
#                   argument.
VOID_VARIADIC_STUB_FUNCTION_DEFINITION = (
    """extern void %(name)s(%(params)s) __attribute__((weak));
void %(name)s(%(params)s) {
  va_list args___;
  va_start(args___, %(last_named_arg)s);
  %(name)s_ptr(%(arg_list)s, va_arg(args___, void*));
  va_end(args___);
}""")

# Template for the preamble for the stub header file with the header guards,
# standard set of includes, and namespace opener.  This template takes the
# following named parameters:
#   guard_name: The macro to use as the header guard.
#   namespace: The namespace for the stub functions.
STUB_HEADER_PREAMBLE = """// This is generated file. Do not modify directly.

#ifndef %(guard_name)s
#define %(guard_name)s

#include <map>
#include <string>
#include <vector>

#include "base/logging.h"

namespace %(namespace)s {
"""

# Template for the end of the stub header. This closes the namespace and the
# header guards.  This template takes the following named parameters:
#   guard_name: The macro to use as the header guard.
#   namespace: The namespace for the stub functions.
STUB_HEADER_CLOSER = """}  // namespace %(namespace)s

#endif  // %(guard_name)s
"""

# The standard includes needed for the stub implementation file.  Takes one
# string substition with the path to the associated stub header file.
IMPLEMENTATION_PREAMBLE = """// This is generated file. Do not modify directly.

#include "%s"

#include <stdlib.h>  // For NULL.
#include <dlfcn.h>   // For dysym, dlopen.

#include <map>
#include <vector>
"""

# The start and end templates for the enum definitions used by the Umbrella
# initializer.
UMBRELLA_ENUM_START = """// Enum and typedef for umbrella initializer.
enum StubModules {
"""
UMBRELLA_ENUM_END = """  kNumStubModules
};

"""

# Start and end of the extern "C" section for the implementation contents.
IMPLEMENTATION_CONTENTS_C_START = """extern "C" {

"""
IMPLEMENTATION_CONTENTS_C_END = """
}  // extern "C"


"""

# Templates for the start and end of a namespace.  Takes one parameter, the
# namespace name.
NAMESPACE_START = """namespace %s {

"""
NAMESPACE_END = """}  // namespace %s

"""

# Comment to include before the section declaring all the function pointers
# used by the stub functions.
FUNCTION_POINTER_SECTION_COMMENT = (
    """// Static pointers that will hold the location of the real function
// implementations after the module has been loaded.
""")

# Template for the module initialization check function.  This template
# takes two parameteres: the function name, and the conditional used to
# verify the module's initialization.
MODULE_INITIALIZATION_CHECK_FUNCTION = (
    """// Returns true if all stubs have been properly initialized.
bool %s() {
  if (%s) {
    return true;
  } else {
    return false;
  }
}

""")

# Template for the line that initialize the stub pointer.  This template takes
# the following named parameters:
#   name: The name of the function.
#   return_type: The return type.
#   params: The parameters to the function.
STUB_POINTER_INITIALIZER = """  %(name)s_ptr =
    reinterpret_cast<%(return_type)s (*)(%(parameters)s)>(
      dlsym(module, "%(name)s"));
    VLOG_IF(1, !%(name)s_ptr) << "Couldn't load %(name)s, dlerror() says:\\n"
        << dlerror();
"""

# Template for module initializer function start and end.  This template takes
# one parameter which is the initializer function name.
MODULE_INITIALIZE_START = """// Initializes the module stubs.
void %s(void* module) {
"""
MODULE_INITIALIZE_END = """}

"""

# Template for module uninitializer function start and end.  This template
# takes one parameter which is the initializer function name.
MODULE_UNINITIALIZE_START = (
    """// Uninitialize the module stubs.  Reset pointers to NULL.
void %s() {
""")
MODULE_UNINITIALIZE_END = """}

"""


# Open namespace and add typedef for internal data structures used by the
# umbrella initializer.
UMBRELLA_INITIALIZER_START = """namespace %s {
typedef std::map<StubModules, void*> StubHandleMap;
"""

# Function close DSOs on error and clean up dangling references.
UMBRELLA_INITIALIZER_CLEANUP_FUNCTION = (
    """static void CloseLibraries(StubHandleMap* stub_handles) {
  for (StubHandleMap::const_iterator it = stub_handles->begin();
       it != stub_handles->end();
       ++it) {
    dlclose(it->second);
  }

  stub_handles->clear();
}
""")

# Function to initialize each DSO for the given paths.
UMBRELLA_INITIALIZER_INITIALIZE_FUNCTION_START = (
    """bool InitializeStubs(const StubPathMap& path_map) {
  StubHandleMap opened_libraries;
  for (int i = 0; i < kNumStubModules; ++i) {
    StubModules cur_module = static_cast<StubModules>(i);
    // If a module is missing, we fail.
    StubPathMap::const_iterator it = path_map.find(cur_module);
    if (it == path_map.end()) {
      CloseLibraries(&opened_libraries);
      return false;
    }

    // Otherwise, attempt to dlopen the library.
    const std::vector<std::string>& paths = it->second;
    bool module_opened = false;
    for (std::vector<std::string>::const_iterator dso_path = paths.begin();
         !module_opened && dso_path != paths.end();
         ++dso_path) {
      void* handle = dlopen(dso_path->c_str(), RTLD_LAZY);
      if (handle != NULL) {
        module_opened = true;
        opened_libraries[cur_module] = handle;
      } else {
        VLOG(1) << "dlopen(" << dso_path->c_str() << ") failed, "
                << "dlerror() says:\\n" << dlerror();
      }
    }

    if (!module_opened) {
      CloseLibraries(&opened_libraries);
      return false;
    }
  }
""")

# Template to generate code to check if each module initializer correctly
# completed, and cleanup on failures.  This template takes the following
# named parameters.
#   conditional: The conditional expression for successful initialization.
#   uninitializers: The statements needed to uninitialize the modules.
UMBRELLA_INITIALIZER_CHECK_AND_CLEANUP = (
    """  // Check that each module is initialized correctly.
  // Close all previously opened libraries on failure.
  if (%(conditional)s) {
    %(uninitializers)s;
    CloseLibraries(&opened_libraries);
    return false;
  }

  return true;
}
""")

# Template for Initialize, Unininitialize, and IsInitialized functions for each
# module.  This template takes the following named parameters:
#   initialize: Name of the Initialize function.
#   uninitialize: Name of the Uninitialize function.
#   is_initialized: Name of the IsInitialized function.
MODULE_FUNCTION_PROTOTYPES = """bool %(is_initialized)s();
void %(initialize)s(void* module);
void %(uninitialize)s();

"""

# Template for umbrella initializer declaration and associated datatypes.
UMBRELLA_INITIALIZER_PROTOTYPE = (
    """typedef std::map<StubModules, std::vector<std::string> > StubPathMap;

// Umbrella initializer for all the modules in this stub file.
bool InitializeStubs(const StubPathMap& path_map);
""")


def ExtractModuleName(infile_path):
  """Infers the module name from the input file path.

  The input filename is supposed to be in the form "ModuleName.sigs".
  This function splits the filename from the extention on that basename of
  the path and returns that as the module name.

  Args:
    infile_path: String holding the path to the input file.

  Returns:
    The module name as a string.
  """
  basename = os.path.basename(infile_path)

  # This loop continously removes suffixes of the filename separated by a "."
  # character.
  while 1:
    new_basename = os.path.splitext(basename)[0]
    if basename == new_basename:
      break
    else:
      basename = new_basename
  return basename


def ParseSignatures(infile):
  """Parses function signatures in the input file.

  This function parses a file of signatures into a list of dictionaries that
  represent the function signatures in the input file.  Each dictionary has
  the following keys:
    return_type: A string with the return type.
    name: A string with the name of the function.
    params: A list of each function parameter declaration (type + name)

  The format of the input file is one C-style function signature per line, no
  trailing semicolon.  Empty lines are allowed.  An empty line is a line that
  consists purely of whitespace.  Lines that begin with a # are considered
  comment lines and are ignored.

  We assume that "int foo(void)" is the same as "int foo()", which is not
  true in C where "int foo()" is equivalent to "int foo(...)".  Our generated
  code is C++, and we do not handle varargs, so this is a case that can be
  ignored for now.

  Args:
    infile: File object holding a text file of function signatures.

  Returns:
    A list of dictionaries, where each dictionary represents one function
    signature.

  Raises:
    BadSignatureError: A line could not be parsed as a signature.
  """
  signatures = []
  for line in infile:
    line = line.strip()
    if line and line[0] != '#':
      m = SIGNATURE_REGEX.match(line)
      if m is None:
        raise BadSignatureError('Unparsable line: %s' % line)
      signatures.append(