// Copyright (C) 2012 The Android Open Source Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//===----------------------------------------------------------------------===//
//                     The LLVM Compiler Infrastructure
//
// This file is dual licensed under the MIT and the University of Illinois Open
// Source Licenses. See LICENSE.TXT for details.
//
//
//  This file implements the "Exception Handling APIs"
//  http://www.codesourcery.com/public/cxx-abi/abi-eh.html
//  http://www.intel.com/design/itanium/downloads/245358.htm
//
//===----------------------------------------------------------------------===//
/*
 * Copyright 2010-2011 PathScale, Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
 * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#include <cstdlib>
#include <unwind.h>

#include "cxxabi_defines.h"
#include "dwarf_helper.h"
#include "helper_func_internal.h"

namespace __cxxabiv1 {

#ifdef __arm__
extern "C" enum type_match_result {
  ctm_failed = 0,
  ctm_succeeded = 1,
  ctm_succeeded_with_ptr_to_base = 2
};


extern "C" type_match_result __attribute__((visibility("default")))
__cxa_type_match(_Unwind_Exception* ucbp,
                 const __shim_type_info* rttip,
                 bool is_reference_type,
                 void** matched_object) {

  __cxa_exception* header = reinterpret_cast<__cxa_exception*>(ucbp+1)-1;
  type_match_result result = ctm_succeeded;

  void* adjustedPtr = header+1;
  if (dynamic_cast<const __pointer_type_info*>(header->exceptionType)) {
    adjustedPtr = *reinterpret_cast<void**>(adjustedPtr);
    result = ctm_succeeded_with_ptr_to_base;
  }

  const __shim_type_info* catch_type = rttip;
  const __shim_type_info* thrown_type =
      static_cast<const __shim_type_info*>(header->exceptionType);
  if (!catch_type || !thrown_type) {
    return ctm_failed;
  }

  if (catch_type->can_catch(thrown_type, adjustedPtr)) {
    *matched_object = adjustedPtr;
    return result;
  }

  return ctm_failed;
}
#endif  // __arm__

namespace {

void terminate_helper(std::terminate_handler t_handler) {
  try {
    t_handler();
    abort();
  } catch (...) {
    abort();
  }
}

void unexpected_helper(std::unexpected_handler u_handler) {
  u_handler();
  std::terminate();
}

}  // namespace

#ifdef __arm__
  extern "C" bool   __attribute__((visibility("default")))
  __cxa_begin_cleanup(_Unwind_Exception* exc) {
    __cxa_eh_globals *globals = __cxa_get_globals();
    __cxa_exception *header = reinterpret_cast<__cxa_exception*>(exc+1)-1;
    bool native = header->unwindHeader.exception_class == __gxx_exception_class;

    if (native) {
      header->cleanupCount += 1;
      if (header->cleanupCount == 1) {  // First time
        header->nextCleanup = globals->cleanupExceptions;
        globals->cleanupExceptions = header;
      }
    } else {
      globals->cleanupExceptions = header;
    }

    return true;
  }

  extern "C" _Unwind_Exception * helper_end_cleanup() {
    __cxa_eh_globals *globals = __cxa_get_globals();
    __cxa_exception* header = globals->cleanupExceptions;

    if (!header) {
      std::terminate();
    }

    if (header->unwindHeader.exception_class == __gxx_exception_class) {
      header->cleanupCount -= 1;
      if (header->cleanupCount == 0) {  // Last one
        globals->cleanupExceptions = header->nextCleanup;
        header->nextCleanup = NULL;
      }
    } else {
      globals->cleanupExceptions = NULL;
    }

    return &header->unwindHeader;
  }

  asm (
  ".pushsection .text.__cxa_end_cleanup    \n"
  ".global __cxa_end_cleanup               \n"
  ".type __cxa_end_cleanup, \"function\"   \n"
  "__cxa_end_cleanup:                      \n"
  " push\t{r1, r2, r3, r4}                 \n"
  " bl helper_end_cleanup                  \n"
  " pop\t{r1, r2, r3, r4}                  \n"
  " bl _Unwind_Resume                      \n"
  " bl abort                               \n"
  ".popsection                             \n"
  );

  extern "C" void __attribute__((visibility("default")))
  __cxa_call_unexpected(void* arg) {
    _Unwind_Exception* unwind_exception = static_cast<_Unwind_Exception*>(arg);
    __cxa_exception* header = reinterpret_cast<__cxa_exception*>(unwind_exception+1)-1;
    bool native_exception = unwind_exception->exception_class == __gxx_exception_class;

    if (!native_exception) {
      __cxa_begin_catch(unwind_exception);    // unexpected is also a handler
      try {
        std::unexpected();
      } catch (...) {
        std::terminate();
      }

      return;
    }

    // Cache previous data first since we will change contents below.
    uint32_t count = unwind_exception->barrier_cache.bitpattern[1];
    uint32_t stride = unwind_exception->barrier_cache.bitpattern[3];
    uint32_t* list = reinterpret_cast<uint32_t*>(
                            unwind_exception->barrier_cache.bitpattern[4]);

    __cxa_begin_catch(unwind_exception);    // unexpected is also a handler
    try {
      unexpected_helper(header->unexpectedHandler);
    } catch (...) {
      // A new exception thrown when calling unexpected.
      bool allow_bad_exception = false;

      for (uint32_t i = 0; i != count; ++i) {
        uint32_t offset = reinterpret_cast<uint32_t>(&list[i * (stride >> 2)]);
        offset = decodeRelocTarget2(offset);
        const __shim_type_info* catch_type = reinterpret_cast<const __shim_type_info*>(offset);

        __cxa_exception* new_header = __cxa_get_globals()->caughtExceptions;
        void* adjustedPtr = new_header + 1;
        if (__cxa_type_match(&new_header->unwindHeader,
                             catch_type,
                             false/* is_ref_type */,
                             &adjustedPtr) != ctm_failed) {
          throw;
        }

        void* null_adjustedPtr = NULL;
        const __shim_type_info* bad_excp =
            static_cast<const __shim_type_info*>(&typeid(std::bad_exception));
        if (catch_type->can_catch(bad_excp, null_adjustedPtr)) {
          allow_bad_exception = true;
        }
      }

      // If no other ones match, throw bad_exception.
      if (allow_bad_exception) {
        __cxa_end_catch();
        __cxa_end_catch();
        throw std::bad_exception();
      }

      terminate_helper(header->terminateHandler);
    }
  }
#else // ! __arm__
  extern "C" void __attribute__((visibility("default")))
  __cxa_call_unexpected(void* arg) {
    _Unwind_Exception* unwind_exception = static_cast<_Unwind_Exception*>(arg);
    if (unwind_exception == 0) {
      call_terminate(unwind_exception);
    }
    __cxa_begin_catch(unwind_exception);    // unexpected is also a handler

    bool native_old_exception = unwind_exception->exception_class == __gxx_exception_class;
    std::unexpected_handler u_handler;
    std::terminate_handler t_handler;
    __cxa_exception* old_exception_header = 0;
    int64_t ttypeIndex;
    const uint8_t* lsda;
    if (native_old_exception) {
      old_exception_header = reinterpret_cast<__cxa_exception*>(unwind_exception+1)-1;
      t_handler = old_exception_header->terminateHandler;
      u_handler = old_exception_header->unexpectedHandler;
      // If unexpected_helper(u_handler) rethrows the same exception,
      //   these values get overwritten by the rethrow.  So save them now:
      ttypeIndex = old_exception_header->handlerSwitchValue;
      lsda = old_exception_header->languageSpecificData;
    } else {
      t_handler = std::get_terminate();
      u_handler = std::get_unexpected();
    }

    try {
      unexpected_helper(u_handler);
    } catch (...) {
      // A new exception thrown when calling unexpected.

      if (!native_old_exception) {
        std::terminate();
      }
      uint8_t lpStartEncoding = *lsda++;
      readEncodedPointer(&lsda, lpStartEncoding);
      uint8_t ttypeEncoding = *lsda++;
      if (ttypeEncoding == DW_EH_PE_omit) {
        terminate_helper(t_handler);
      }
      uintptr_t classInfoOffset = readULEB128(&lsda);
      const uint8_t* classInfo = lsda + classInfoOffset;
      __cxa_eh_globals* globals = __cxa_get_globals_fast();
      __cxa_exception* new_exception_header = globals->caughtExceptions;
      if (new_exception_header == 0) {  // This shouldn't be able to happen!
        terminate_helper(t_handler);
      }
      bool native_new_exception =
        new_exception_header->unwindHeader.exception_class == __gxx_exception_class;

      if (native_new_exception && (new_exception_header != old_exception_header)) {
        const std::type_info* excpType = new_exception_header->exceptionType;
        if (!canExceptionSpecCatch(ttypeIndex, classInfo, ttypeEncoding,
                                   excpType, new_exception_header+1, unwind_exception)) {
          // We need to __cxa_end_catch, but for the old exception,
          //   not the new one.  This is a little tricky ...
          // Disguise new_exception_header as a rethrown exception, but
          //   don't actually rethrow it.  This means you can temporarily
          //   end the catch clause enclosing new_exception_header without
          //   __cxa_end_catch destroying new_exception_header.
          new_exception_header->handlerCount = -new_exception_header->handlerCount;
          globals->uncaughtExceptions += 1;
          __cxa_end_catch();
          __cxa_end_catch();
          __cxa_begin_catch(&new_exception_header->unwindHeader);
          throw;
        }
      }

      const std::type_info* excpType = &typeid(std::bad_exception);
      if (!canExceptionSpecCatch(ttypeIndex, classInfo, ttypeEncoding,
                                 excpType, NULL, unwind_exception)) {
        __cxa_end_catch();
        __cxa_end_catch();
        throw std::bad_exception();
      }
    } // catch (...)

    // Call terminate after unexpected normally done
    terminate_helper(t_handler);
  }
#endif // __arm__

} // namespace __cxxabiv1