//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/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!

#include <boost/exception/exception.hpp>
#include <boost/shared_ptr.hpp>
#ifndef BOOST_NO_RTTI
#include <typeinfo>
#endif
#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)
    int const exception_info_offset=0x80;
#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;
    unsigned const cpp_exception_parameter_count=3;

    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
        {
        normal_copy_constructor_ptr normal_copy_constructor;
        copy_constructor_with_virtual_base_ptr copy_constructor_with_virtual_base;
        };

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

    struct
    cpp_type_info
        {
        unsigned flags;
#ifndef BOOST_NO_RTTI
        void const * type_info;
#else
        std::type_info * type_info;
#endif
        int this_offset;
        int vbase_descr;
        int vbase_offset;
        unsigned long size;
        cpp_copy_constructor copy_constructor;
        };

    struct
    cpp_type_info_table
        {
        unsigned count;
        const cpp_type_info * info[1];
        };

    struct
    cpp_exception_type
        {
        unsigned flags;
        destructor_ptr destructor;
        void(*custom_handler)();
        cpp_type_info_table const * type_info_table;
        };

    struct
    exception_object_deleter
        {
        cpp_exception_type const & et_;

        exception_object_deleter( cpp_exception_type const & et ):
            et_(et)
            {
            }

        void
        operator()( void * obj )
            {
            BOOST_ASSERT(obj!=0);
            dummy_exception_type * dummy_exception_ptr=reinterpret_cast<dummy_exception_type *>(obj);
            (dummy_exception_ptr->*(et_.destructor))();
            free(obj);
            }
        };

    cpp_type_info const &
    get_cpp_type_info( cpp_exception_type const & et )
        {
        cpp_type_info const * ti = et.type_info_table->info[0];
        BOOST_ASSERT(ti!=0);
        return *ti;
        }

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

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

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

        cpp_exception_type const & et_;
        pdalboost::shared_ptr<void> exc_;

        public:

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

        ~cloned_exception() throw()
            {
            }

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

        void
        rethrow() const
            {
            cpp_type_info const & ti=get_cpp_type_info(et_);
            void * dst = _alloca(ti.size);
            copy_msvc_exception(dst,exc_.get(),ti);
            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_);
            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, pdalboost::exception_detail::clone_base const * & ptr, void * info_ )
        {
        BOOST_ASSERT(exception_info_offset>=0);
        BOOST_ASSERT(info_!=0);
        EXCEPTION_POINTERS * info=reinterpret_cast<EXCEPTION_POINTERS *>(info_);
        EXCEPTION_RECORD * record=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(
                            reinterpret_cast<void *>(record->ExceptionInformation[1]),
                            *reinterpret_cast<cpp_exception_type const *>(record->ExceptionInformation[2]));
                    result = pdalboost::exception_detail::clone_current_exception_result::success;
                    }
                catch(
                std::bad_alloc & )
                    {
                    result = pdalboost::exception_detail::clone_current_exception_result::bad_alloc;
                    }
                catch(
                ... )
                    {
                    result = pdalboost::exception_detail::clone_current_exception_result::bad_exception;
                    }
            }
        return EXCEPTION_EXECUTE_HANDLER;
        }
    }

namespace
pdalboost
    {
    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.

#include <boost/config.hpp>

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

#endif
