//Copyright (c) 2006-2009 Emil Dotchevski and Reverge Studios, Inc.

//Distributed under the Boost Software License, Version 1.0. (See accompanying
//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

//This MSVC-specific cpp file implements non-intrusive cloning of exception objects.
//Based on an exception_ptr implementation by Anthony Williams.

#ifdef BOOST_NO_EXCEPTIONS
#error This file requires exception handling to be enabled.
#endif

#include <boost/config.hpp>
#include <boost/exception/detail/clone_current_exception.hpp>

#if defined(BOOST_ENABLE_NON_INTRUSIVE_EXCEPTION_PTR) && defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))

//Non-intrusive cloning support implemented below, only for MSVC versions mentioned above.
//Thanks Anthony Williams!
//Thanks to Martin Weiss for implementing 64-bit support!

#include <boost/exception/exception.hpp>
#include <boost/shared_ptr.hpp>
#include <windows.h>
#include <malloc.h>

namespace
    {
    unsigned const exception_maximum_parameters=15;
    unsigned const exception_noncontinuable=1;

#if _MSC_VER==1310
    int const exception_info_offset=0x74;
#elif ((_MSC_VER==1400 || _MSC_VER==1500) && !defined _M_X64)
    int const exception_info_offset=0x80;
#elif ((_MSC_VER==1400 || _MSC_VER==1500) && defined _M_X64)
    int const exception_info_offset=0xE0;
#else
    int const exception_info_offset=-1;
#endif

    struct
    exception_record
        {
        unsigned long ExceptionCode;
        unsigned long ExceptionFlags;
        exception_record * ExceptionRecord;
        void * ExceptionAddress;
        unsigned long NumberParameters;
        ULONG_PTR ExceptionInformation[exception_maximum_parameters];
        };

    struct
    exception_pointers
        {
        exception_record * ExceptionRecord;
        void * ContextRecord;
        };

    unsigned const cpp_exception_code=0xE06D7363;
    unsigned const cpp_exception_magic_flag=0x19930520;
#ifdef _M_X64
    unsigned const cpp_exception_parameter_count=4;
#else
    unsigned const cpp_exception_parameter_count=3;
#endif

    struct
    dummy_exception_type
        {
        };

    typedef int(dummy_exception_type::*normal_copy_constructor_ptr)(void * src);
    typedef int(dummy_exception_type::*copy_constructor_with_virtual_base_ptr)(void * src,void * dst);
    typedef void (dummy_exception_type::*destructor_ptr)();

    union
    cpp_copy_constructor
        {
        void * address;
        normal_copy_constructor_ptr normal_copy_constructor;
        copy_constructor_with_virtual_base_ptr copy_constructor_with_virtual_base;
        };

    union
    cpp_destructor
        {
        void * address;
        destructor_ptr destructor;
        };

    enum
    cpp_type_flags
        {
        class_is_simple_type=1,
        class_has_virtual_base=4
        };

    // ATTENTION: On x86 fields such as type_info and copy_constructor are really pointers
    // but on 64bit these are 32bit offsets from HINSTANCE. Hints on the 64bit handling from
    // http://blogs.msdn.com/b/oldnewthing/archive/2010/07/30/10044061.aspx .
    struct
    cpp_type_info
        {
        unsigned flags;
        int type_info;
        int this_offset;
        int vbase_descr;
        int vbase_offset;
        unsigned long size;
        int copy_constructor;
        };

    struct
    cpp_type_info_table
        {
        unsigned count;
        int info;
        };

    struct
    cpp_exception_type
        {
        unsigned flags;
        int destructor;
        int custom_handler;
        int type_info_table;
        };

    struct
    exception_object_deleter
        {
        cpp_exception_type const & et_;
        size_t image_base_;

        exception_object_deleter( cpp_exception_type const & et, size_t image_base ):
            et_(et),
            image_base_(image_base)
            {
            }

        void
        operator()( void * obj )
            {
            BOOST_ASSERT(obj!=0);
            dummy_exception_type* dummy_exception_ptr = static_cast<dummy_exception_type *>(obj);
            if( et_.destructor )
                {
                cpp_destructor destructor;
                destructor.address = reinterpret_cast<void *>(et_.destructor + image_base_);
                (dummy_exception_ptr->*(destructor.destructor))();
                }
            free(obj);
            }
        };

    cpp_type_info const &
    get_cpp_type_info( cpp_exception_type const & et, size_t image_base )
        {
        cpp_type_info_table * const typearray = reinterpret_cast<cpp_type_info_table * const>(et.type_info_table + image_base);
        cpp_type_info * const ti = reinterpret_cast<cpp_type_info * const>(typearray->info + image_base);
        BOOST_ASSERT(ti!=0);
        return *ti;
        }

    void
    copy_msvc_exception( void * dst, void * src, cpp_type_info const & ti, size_t image_base )
        {
        cpp_copy_constructor copy_constructor;
        copy_constructor.address = reinterpret_cast<void *>(ti.copy_constructor + image_base);

        if( !(ti.flags & class_is_simple_type) && copy_constructor.normal_copy_constructor )
            {
            dummy_exception_type * dummy_exception_ptr = static_cast<dummy_exception_type *>(dst);
            if( ti.flags & class_has_virtual_base )
                (dummy_exception_ptr->*(copy_constructor.copy_constructor_with_virtual_base))(src,dst);
            else
                (dummy_exception_ptr->*(copy_constructor.normal_copy_constructor))(src);
            }
        else
            memmove(dst,src,ti.size);
        }

    boost::shared_ptr<void>
    clone_msvc_exception( void * src, cpp_exception_type const & et, size_t image_base )
        {
        BOOST_ASSERT(src!=0);
        cpp_type_info const & ti=get_cpp_type_info(et,image_base);
        if( void * dst = malloc(ti.size) )
            {
            try
                {
                copy_msvc_exception(dst,src,ti,image_base);
                }
            catch(
            ... )
                {
                free(dst);
                throw;
                }
            return boost::shared_ptr<void>(dst,exception_object_deleter(et,image_base));
            }
        else
            throw std::bad_alloc();
        }

    class
    cloned_exception:
        public boost::exception_detail::clone_base
        {
        cloned_exception( cloned_exception const & );
        cloned_exception & operator=( cloned_exception const & );

        cpp_exception_type const & et_;
        size_t image_base_;
        boost::shared_ptr<void> exc_;

        public:
        cloned_exception( EXCEPTION_RECORD const * record ):
            et_(*reinterpret_cast<cpp_exception_type const *>(record->ExceptionInformation[2])),
            image_base_((cpp_exception_parameter_count==4) ? record->ExceptionInformation[3] : 0),
            exc_(clone_msvc_exception(reinterpret_cast<void *>(record->ExceptionInformation[1]),et_,image_base_))
            {
            }

        cloned_exception( void * exc, cpp_exception_type const & et, size_t image_base ):
            et_(et),
            image_base_(image_base),
            exc_(clone_msvc_exception(exc,et_,image_base))
            {
            }

        ~cloned_exception() BOOST_NOEXCEPT_OR_NOTHROW
            {
            }

        boost::exception_detail::clone_base const *
        clone() const
            {
            return new cloned_exception(exc_.get(),et_,image_base_);
            }

        void
        rethrow() const
            {
            cpp_type_info const & ti=get_cpp_type_info(et_,image_base_);
            void * dst = _alloca(ti.size);
            copy_msvc_exception(dst,exc_.get(),ti,image_base_);
            ULONG_PTR args[cpp_exception_parameter_count];
            args[0]=cpp_exception_magic_flag;
            args[1]=reinterpret_cast<ULONG_PTR>(dst);
            args[2]=reinterpret_cast<ULONG_PTR>(&et_);
            if (cpp_exception_parameter_count==4)
                args[3]=image_base_;

            RaiseException(cpp_exception_code,EXCEPTION_NONCONTINUABLE,cpp_exception_parameter_count,args);
            }
        };

    bool
    is_cpp_exception( EXCEPTION_RECORD const * record )
        {
        return record && 
            (record->ExceptionCode==cpp_exception_code) &&
            (record->NumberParameters==cpp_exception_parameter_count) &&
            (record->ExceptionInformation[0]==cpp_exception_magic_flag);
        }

    unsigned long
    exception_cloning_filter( int & result, boost::exception_detail::clone_base const * & ptr, void * info_ )
        {
        BOOST_ASSERT(exception_info_offset>=0);
        BOOST_ASSERT(info_!=0);
        EXCEPTION_RECORD* record = static_cast<EXCEPTION_POINTERS *>(info_)->ExceptionRecord;
        if( is_cpp_exception(record) )
            {
            if( !record->ExceptionInformation[2] )
                record = *reinterpret_cast<EXCEPTION_RECORD * *>(reinterpret_cast<char *>(_errno())+exception_info_offset);
            if( is_cpp_exception(record) && record->ExceptionInformation[2] )
                try
                    {
                    ptr = new cloned_exception(record);
                    result = boost::exception_detail::clone_current_exception_result::success;
                    }
                catch(
                std::bad_alloc & )
                    {
                    result = boost::exception_detail::clone_current_exception_result::bad_alloc;
                    }
                catch(
                ... )
                    {
                    result = boost::exception_detail::clone_current_exception_result::bad_exception;
                    }
            }
        return EXCEPTION_EXECUTE_HANDLER;
        }
    }

namespace
boost
    {
    namespace
    exception_detail
        {
        int
        clone_current_exception_non_intrusive( clone_base const * & cloned )
            {
            BOOST_ASSERT(!cloned);
            int result = clone_current_exception_result::not_supported;
            if( exception_info_offset>=0 )
                {
                 clone_base const * ptr=0;
                __try
                    {
                    throw;
                    }
                __except(exception_cloning_filter(result,ptr,GetExceptionInformation()))
                    {
                    }
                if( result==clone_current_exception_result::success )
                    cloned=ptr;
                }
            BOOST_ASSERT(result!=clone_current_exception_result::success || cloned);
            return result;
            }
        }
    }

#else

//On all other compilers, return clone_current_exception_result::not_supported.
//On such platforms, only the intrusive enable_current_exception() cloning will work.

namespace
boost
    {
    namespace
    exception_detail
        {
        int
        clone_current_exception_non_intrusive( clone_base const * & )
            {
            return clone_current_exception_result::not_supported;
            }
        }
    }

#endif
