From 5ff21f9c2d026adb9e4c59ca3b926dc67fe6d869 Mon Sep 17 00:00:00 2001
From: JamesJCode <13408010-JamesJCode@users.noreply.gitlab.com>
Date: Fri, 27 Dec 2024 01:49:16 +0000
Subject: [PATCH] Add ki::any and ki::any_cast for any_casts across translation
units
ki::any is a standards-compliant implementation, based on the GCC
standard library. However, it uses type_info::hash_code() to check
the validitiy of a ki:any_cast, rather than comparing the type_info
objects directly. This comparison, used in the standard
implementations, is fragile across translation unit boundaries when
built with Clang.
---
common/tool/tool_manager.cpp | 4 +-
include/ki_any.h | 623 +++++++++++++++++++++++
include/tool/tool_action.h | 10 +-
include/tool/tool_event.h | 13 +-
include/tool/tool_manager.h | 40 +-
qa/tests/common/CMakeLists.txt | 1 +
qa/tests/common/test_ki_any.cpp | 840 ++++++++++++++++++++++++++++++++
7 files changed, 1499 insertions(+), 32 deletions(-)
create mode 100644 include/ki_any.h
create mode 100644 qa/tests/common/test_ki_any.cpp
diff --git a/common/tool/tool_manager.cpp b/common/tool/tool_manager.cpp
index b46d38a66a..0ff0385364 100644
--- a/common/tool/tool_manager.cpp
+++ b/common/tool/tool_manager.cpp
@@ -290,7 +290,7 @@ bool TOOL_MANAGER::InvokeTool( const std::string& aToolName )
}
-bool TOOL_MANAGER::doRunAction( const std::string& aActionName, bool aNow, const std::any& aParam,
+bool TOOL_MANAGER::doRunAction( const std::string& aActionName, bool aNow, const ki::any& aParam,
COMMIT* aCommit )
{
TOOL_ACTION* action = m_actionMgr->FindAction( aActionName );
@@ -325,7 +325,7 @@ VECTOR2D TOOL_MANAGER::GetCursorPosition() const
}
-bool TOOL_MANAGER::doRunAction( const TOOL_ACTION& aAction, bool aNow, const std::any& aParam,
+bool TOOL_MANAGER::doRunAction( const TOOL_ACTION& aAction, bool aNow, const ki::any& aParam,
COMMIT* aCommit )
{
if( m_shuttingDown )
diff --git a/include/ki_any.h b/include/ki_any.h
new file mode 100644
index 0000000000..35a9645d9a
--- /dev/null
+++ b/include/ki_any.h
@@ -0,0 +1,623 @@
+/*
+* This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+// This code is a modified version of the GCC standard library implementation (see original licence below):
+
+// Copyright (C) 2014-2024 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library. This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// Under Section 7 of GPL version 3, you are granted additional
+// permissions described in the GCC Runtime Library Exception, version
+// 3.1, as published by the Free Software Foundation.
+//
+// You should have received a copy of the GNU General Public License and
+// a copy of the GCC Runtime Library Exception along with this program;
+// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
+// .
+
+
+/** An implementation of std::any_cast, which uses type_info::hash_code to check validity of
+ * cast types. This is required as Clang compares types as being equivalent based on their
+ * type_info pointer locations. These are not guaranteed to be the same with identical types
+ * linked in multiple targets from shared libraries. The current Clang implementation of
+ * type_info::hash_code is based on the type names, which should be consistent across translation
+ * units.
+ */
+#ifndef INCLUDE_KI_ANY_H_
+#define INCLUDE_KI_ANY_H_
+
+#include
+#include
+#include
+#include
+
+namespace ki
+{
+
+/*
+ * Disambiguation helpers
+ */
+
+template
+inline constexpr bool is_in_place_type_v = false;
+
+template
+inline constexpr bool is_in_place_type_v> = true;
+
+/**
+ * @brief Exception class thrown by a failed @c any_cast
+ */
+class bad_any_cast final : public std::bad_cast
+{
+public:
+ const char* what() const noexcept override { return "bad ki::any_cast"; }
+};
+
+/**
+ * @brief A type-safe container of any type.
+ *
+ * An `any` object's state is either empty or it stores a contained object
+ * of CopyConstructible type.
+ */
+class any
+{
+ // Holds either a pointer to a heap object or the contained object itself
+ union Storage
+ {
+ constexpr Storage() : m_ptr{ nullptr } {}
+
+ // Prevent trivial copies of this type as the buffer might hold a non-POD
+ Storage( const Storage& ) = delete;
+ Storage& operator=( const Storage& ) = delete;
+
+ void* m_ptr;
+ unsigned char m_buffer[sizeof( m_ptr )];
+ };
+
+ template ,
+ bool Fits = ( sizeof( T ) <= sizeof( Storage ) )
+ && ( alignof( T ) <= alignof( Storage ) )>
+ using Use_Internal_Storage = std::integral_constant;
+
+ template
+ struct Manager_Internal; // uses small-object optimization
+
+ template
+ struct Manager_External; // creates contained object on the heap
+
+ template
+ using Manager = std::conditional_t::value, Manager_Internal,
+ Manager_External>;
+
+ template >
+ using decay_if_not_any = std::enable_if_t, V>;
+
+ /// @brief Emplace with an object created from @p args as the contained object
+ template >
+ void do_emplace( Args&&... args )
+ {
+ reset();
+ Mgr::do_create( m_storage, std::forward( args )... );
+ m_manager = &Mgr::m_manage_fn;
+ }
+
+ /// @brief Emplace with an object created from @p il and @p args as the contained object
+ template >
+ void do_emplace( std::initializer_list il, Args&&... args )
+ {
+ reset();
+ Mgr::do_create( m_storage, il, std::forward( args )... );
+ m_manager = &Mgr::m_manage_fn;
+ }
+
+ template
+ using any_constructible =
+ std::enable_if && std::is_constructible_v,
+ Res>;
+
+ template
+ using any_constructible_t = typename any_constructible::type;
+
+ template
+ using any_emplace_t = typename any_constructible::type;
+
+public:
+ /// @brief Default constructor, creates an empty object
+ constexpr any() noexcept : m_manager( nullptr ) {}
+
+ /// @brief Copy constructor, copies the state of @p other
+ any( const any& other )
+ {
+ if( !other.has_value() )
+ {
+ m_manager = nullptr;
+ }
+ else
+ {
+ Arg arg;
+ arg.m_any = this;
+ other.m_manager( Op_Clone, &other, &arg );
+ }
+ }
+
+ /// @brief Move constructor, transfer the state from @p other
+ any( any&& other ) noexcept
+ {
+ if( !other.has_value() )
+ {
+ m_manager = nullptr;
+ }
+ else
+ {
+ Arg arg;
+ arg.m_any = this;
+ other.m_manager( Op_Xfer, &other, &arg );
+ }
+ }
+
+ /// @brief Construct with a copy of @p value as the contained object
+ template , typename Mgr = Manager,
+ std::enable_if_t && !is_in_place_type_v, bool> =
+ true>
+
+ any( T&& value ) : m_manager( &Mgr::m_manage_fn )
+ {
+ Mgr::do_create( m_storage, std::forward( value ) );
+ }
+
+ /// @brief Construct with an object created from @p args as the contained object
+ template , typename Mgr = Manager,
+ any_constructible_t = false>
+ explicit any( std::in_place_type_t, Args&&... args ) : m_manager( &Mgr::m_manage_fn )
+ {
+ Mgr::do_create( m_storage, std::forward( args )... );
+ }
+
+ /// @brief Construct with an object created from @p il and @p args as the contained object
+ template ,
+ typename Mgr = Manager,
+ any_constructible_t&, Args&&...> = false>
+ explicit any( std::in_place_type_t, std::initializer_list il, Args&&... args ) :
+ m_manager( &Mgr::m_manage_fn )
+ {
+ Mgr::do_create( m_storage, il, std::forward( args )... );
+ }
+
+ /// @brief Destructor, calls @c reset()
+ ~any() { reset(); }
+
+ /// @brief Copy the state of another object
+ any& operator=( const any& rhs )
+ {
+ *this = any( rhs );
+ return *this;
+ }
+
+ /// @brief Move assignment operator
+ any& operator=( any&& rhs ) noexcept
+ {
+ if( !rhs.has_value() )
+ {
+ reset();
+ }
+ else if( this != &rhs )
+ {
+ reset();
+ Arg arg;
+ arg.m_any = this;
+ rhs.m_manager( Op_Xfer, &rhs, &arg );
+ }
+
+ return *this;
+ }
+
+ /// Store a copy of @p rhs as the contained object
+ template
+ std::enable_if_t>, any&> operator=( T&& rhs )
+ {
+ *this = any( std::forward( rhs ) );
+ return *this;
+ }
+
+ /// Emplace with an object created from @p args as the contained object
+ template
+ any_emplace_t, Args...> emplace( Args&&... args )
+ {
+ using V = std::decay_t;
+ do_emplace( std::forward( args )... );
+ return *any::Manager::do_access( m_storage );
+ }
+
+ /// Emplace with an object created from @p il and @p args as the contained object
+ template
+ any_emplace_t, std::initializer_list&, Args&&...>
+ emplace( std::initializer_list il, Args&&... args )
+ {
+ using V = std::decay_t;
+ do_emplace( il, std::forward( args )... );
+ return *any::Manager::do_access( m_storage );
+ }
+
+ /// If not empty, destroys the contained object
+ void reset() noexcept
+ {
+ if( has_value() )
+ {
+ m_manager( Op_Destroy, this, nullptr );
+ m_manager = nullptr;
+ }
+ }
+
+ /// Exchange state with another object
+ void swap( any& rhs ) noexcept
+ {
+ if( !has_value() && !rhs.has_value() )
+ return;
+
+ if( has_value() && rhs.has_value() )
+ {
+ if( this == &rhs )
+ return;
+
+ any tmp;
+ Arg arg;
+ arg.m_any = &tmp;
+ rhs.m_manager( Op_Xfer, &rhs, &arg );
+ arg.m_any = &rhs;
+ m_manager( Op_Xfer, this, &arg );
+ arg.m_any = this;
+ tmp.m_manager( Op_Xfer, &tmp, &arg );
+ }
+ else
+ {
+ any* empty = !has_value() ? this : &rhs;
+ any* full = !has_value() ? &rhs : this;
+ Arg arg;
+ arg.m_any = empty;
+ full->m_manager( Op_Xfer, full, &arg );
+ }
+ }
+
+ /// Reports whether there is a contained object or not
+ bool has_value() const noexcept { return m_manager != nullptr; }
+
+
+ /// The @c typeid of the contained object, or @c typeid(void) if empty
+ const std::type_info& type() const noexcept
+ {
+ if( !has_value() )
+ return typeid( void );
+
+ Arg arg;
+ m_manager( Op_Get_Type_Info, this, &arg );
+ return *arg.m_typeinfo;
+ }
+
+ /// @cond undocumented
+ template
+ static constexpr bool is_valid_any_cast()
+ {
+ return std::is_reference_v || std::is_copy_constructible_v;
+ }
+ /// @endcond
+
+private:
+ enum Op
+ {
+ Op_Access,
+ Op_Get_Type_Info,
+ Op_Clone,
+ Op_Destroy,
+ Op_Xfer
+ };
+
+ union Arg
+ {
+ void* m_obj;
+ const std::type_info* m_typeinfo;
+ any* m_any;
+ };
+
+ void ( *m_manager )( Op, const any*, Arg* );
+ Storage m_storage;
+
+ /// @cond undocumented
+ template
+ friend void* any_caster( const any* any );
+ /// @endcond
+
+ // Manages in-place contained object
+ template
+ struct Manager_Internal
+ {
+ static void m_manage_fn( Op which, const any* any, Arg* arg );
+
+ template
+ static void do_create( Storage& storage, U&& value )
+ {
+ void* addr = &storage.m_buffer;
+ ::new( addr ) T( std::forward( value ) );
+ }
+
+ template
+ static void do_create( Storage& storage, Args&&... args )
+ {
+ void* addr = &storage.m_buffer;
+ ::new( addr ) T( std::forward( args )... );
+ }
+
+ static T* do_access( const Storage& storage )
+ {
+ // The contained object is in storage.m_buffer
+ const void* addr = &storage.m_buffer;
+ return static_cast( const_cast( addr ) );
+ }
+ };
+
+ // Manages externally (heap) contained object
+ template
+ struct Manager_External
+ {
+ static void m_manage_fn( Op which, const any* any, Arg* arg );
+
+ template
+ static void do_create( Storage& storage, U&& value )
+ {
+ storage.m_ptr = new T( std::forward( value ) );
+ }
+ template
+ static void do_create( Storage& storage, Args&&... args )
+ {
+ storage.m_ptr = new T( std::forward( args )... );
+ }
+ static T* do_access( const Storage& storage )
+ {
+ // The contained object is in *storage.m_ptr
+ return static_cast( storage.m_ptr );
+ }
+ };
+};
+
+/// Exchange the states of two @c any objects
+inline void swap( any& x, any& y ) noexcept
+{
+ x.swap( y );
+}
+
+/// Create a `any` holding a `T` constructed from `args...`
+template
+std::enable_if_t, Args...>, any>
+make_any( Args&&... args )
+{
+ return any( std::in_place_type, std::forward( args )... );
+}
+
+/// Create an `any` holding a `T` constructed from `il` and `args...`.
+template
+std::enable_if_t<
+ std::is_constructible_v, std::initializer_list&, Args...>,
+ any>
+make_any( std::initializer_list il, Args&&... args )
+{
+ return any( std::in_place_type, il, std::forward( args )... );
+}
+
+/**
+ * @brief Access the contained object
+ *
+ * @tparam ValueType A const-reference or CopyConstructible type.
+ * @param any The object to access.
+ * @return The contained object.
+ * @throw bad_any_cast If
+ * any.type() != typeid(remove_reference_t)
+ *
+ */
+template
+ValueType any_cast( const any& any )
+{
+ using U = std::remove_cvref_t;
+
+ static_assert( any::is_valid_any_cast(),
+ "Template argument must be a reference or CopyConstructible type" );
+ static_assert( std::is_constructible_v,
+ "Template argument must be constructible from a const value" );
+
+ auto p = any_cast( &any );
+
+ if( p )
+ return static_cast( *p );
+
+ throw bad_any_cast{};
+}
+
+/**
+ * @brief Access the contained object.
+ *
+ * @tparam ValueType A reference or CopyConstructible type.
+ * @param any The object to access.
+ * @return The contained object.
+ * @throw bad_any_cast If
+ * any.type() != typeid(remove_reference_t)
+ *
+ * @{
+ */
+template
+ValueType any_cast( any& any )
+{
+ using U = std::remove_cvref_t;
+
+ static_assert( any::is_valid_any_cast(),
+ "Template argument must be a reference or CopyConstructible type" );
+ static_assert( std::is_constructible_v,
+ "Template argument must be constructible from an lvalue" );
+
+ auto p = any_cast( &any );
+
+ if( p )
+ return static_cast( *p );
+
+ throw bad_any_cast{};
+}
+
+template
+ValueType any_cast( any&& any )
+{
+ using U = std::remove_cvref_t;
+
+ static_assert( any::is_valid_any_cast(),
+ "Template argument must be a reference or CopyConstructible type" );
+ static_assert( std::is_constructible_v,
+ "Template argument must be constructible from an rvalue" );
+
+ auto p = any_cast( &any );
+
+ if( p )
+ return static_cast( std::move( *p ) );
+
+ throw bad_any_cast{};
+}
+
+/// @}
+
+/// @cond undocumented
+template
+void* any_caster( const any* any )
+{
+ // any_cast returns non-null if any->type() == typeid(T) and
+ // typeid(T) ignores cv-qualifiers so remove them:
+ using U = std::remove_cv_t;
+
+ if constexpr( !std::is_same_v, U> )
+ {
+ // The contained value has a decayed type, so if decay_t is not U,
+ // then it's not possible to have a contained value of type U
+ return nullptr;
+ }
+ else if constexpr( !std::is_copy_constructible_v )
+ {
+ // Only copy constructible types can be used for contained values
+ return nullptr;
+ }
+ else if( any->m_manager == &any::Manager::m_manage_fn
+ || any->type().hash_code() == typeid( T ).hash_code() )
+ {
+ return any::Manager::do_access( any->m_storage );
+ }
+
+ return nullptr;
+}
+/// @endcond
+
+/**
+ * @brief Access the contained object.
+ *
+ * @tparam ValueType The type of the contained object.
+ * @param any A pointer to the object to access.
+ * @return The address of the contained object if
+ * any != nullptr && any.type() == typeid(ValueType)
+ * , otherwise a null pointer.
+ *
+ * @{
+ */
+template
+const ValueType* any_cast( const any* any ) noexcept
+{
+ static_assert( !std::is_void_v );
+
+ // As an optimization, don't bother instantiating any_caster for
+ // function types, since std::any can only hold objects
+ if constexpr( std::is_object_v )
+ {
+ if( any )
+ return static_cast( any_caster( any ) );
+ }
+
+ return nullptr;
+}
+
+template
+ValueType* any_cast( any* any ) noexcept
+{
+ static_assert( !std::is_void_v );
+
+ if constexpr( std::is_object_v )
+ if( any )
+ return static_cast( any_caster( any ) );
+ return nullptr;
+}
+/// @}
+
+template
+void any::Manager_Internal::m_manage_fn( Op which, const any* any, Arg* arg )
+{
+ // The contained object is in m_storage.m_buffer
+ auto ptr = reinterpret_cast( &any->m_storage.m_buffer );
+ switch( which )
+ {
+ case Op_Access: arg->m_obj = const_cast( ptr ); break;
+ case Op_Get_Type_Info: arg->m_typeinfo = &typeid( T ); break;
+ case Op_Clone:
+ ::new( &arg->m_any->m_storage.m_buffer ) T( *ptr );
+ arg->m_any->m_manager = any->m_manager;
+ break;
+ case Op_Destroy: ptr->~T(); break;
+ case Op_Xfer:
+ ::new( &arg->m_any->m_storage.m_buffer ) T( std::move( *const_cast( ptr ) ) );
+ ptr->~T();
+ arg->m_any->m_manager = any->m_manager;
+ const_cast( any )->m_manager = nullptr;
+ break;
+ }
+}
+
+template
+void any::Manager_External::m_manage_fn( Op which, const any* any, Arg* arg )
+{
+ // The contained object is *m_storage.m_ptr
+ auto ptr = static_cast( any->m_storage.m_ptr );
+ switch( which )
+ {
+ case Op_Access: arg->m_obj = const_cast( ptr ); break;
+ case Op_Get_Type_Info: arg->m_typeinfo = &typeid( T ); break;
+ case Op_Clone:
+ arg->m_any->m_storage.m_ptr = new T( *ptr );
+ arg->m_any->m_manager = any->m_manager;
+ break;
+ case Op_Destroy: delete ptr; break;
+ case Op_Xfer:
+ arg->m_any->m_storage.m_ptr = any->m_storage.m_ptr;
+ arg->m_any->m_manager = any->m_manager;
+ const_cast( any )->m_manager = nullptr;
+ break;
+ }
+}
+
+} // namespace ki
+
+#endif // INCLUDE_KI_ANY_H_
diff --git a/include/tool/tool_action.h b/include/tool/tool_action.h
index 5c242d1e08..e709c559d4 100644
--- a/include/tool/tool_action.h
+++ b/include/tool/tool_action.h
@@ -28,12 +28,12 @@
#ifndef __TOOL_ACTION_H
#define __TOOL_ACTION_H
-#include
#include
#include
#include
#include
+#include
#include
class TOOL_EVENT;
@@ -251,7 +251,7 @@ protected:
std::optional m_group;
- std::any m_param;
+ ki::any m_param;
};
/**
@@ -377,9 +377,9 @@ public:
try
{
- param = std::any_cast( m_param );
+ param = ki::any_cast( m_param );
}
- catch( const std::bad_any_cast& e )
+ catch( const ki::bad_any_cast& e )
{
wxASSERT_MSG( false,
wxString::Format( "Requested parameter type %s from action with parameter type %s.",
@@ -455,7 +455,7 @@ protected:
std::optional m_uiid; // ID to use when interacting with the UI (if empty, generate one)
TOOL_ACTION_FLAGS m_flags;
- std::any m_param; // Generic parameter
+ ki::any m_param; // Generic parameter
};
#endif
diff --git a/include/tool/tool_event.h b/include/tool/tool_event.h
index 4ff93b9e05..4aedc6b05f 100644
--- a/include/tool/tool_event.h
+++ b/include/tool/tool_event.h
@@ -27,11 +27,12 @@
#ifndef __TOOL_EVENT_H
#define __TOOL_EVENT_H
-#include
#include
#include
#include
+#include
+
#include