unique_ptr ownership, safer virtual interfaces, and pch/unity speedups and more

This commit is contained in:
dword64 2026-04-04 20:13:26 +02:00
parent 88d01439dd
commit 667eea2b1e
27 changed files with 257 additions and 129 deletions

View file

@ -1,5 +1,5 @@
# Require at least CMake version 3.14.5 or later for FetchContent
cmake_minimum_required(VERSION 3.14.5)
# Require at least CMake version 3.16 for FetchContent and target_precompiled_headers
cmake_minimum_required(VERSION 3.16)
# Define the VTIL project
project(VTIL-Core)
@ -14,6 +14,8 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
endif()
option(VTIL_BUILD_TESTS "Build tests" ${VTIL_ROOT_PROJECT})
option(VTIL_UNITY_BUILD "Enable unity build for faster compilation" OFF)
option(VTIL_SANITIZE_THREADS "Enable ThreadSanitizer (Clang/GCC only)" OFF)
# Load the dependencies
set(CMAKE_FOLDER "VTIL-Core/Dependencies")

View file

@ -14,3 +14,9 @@ source_group(TREE ${PROJECT_SOURCE_DIR} FILES ${SOURCES} ${INCLUDES})
target_include_directories(${PROJECT_NAME} PUBLIC includes)
target_link_libraries(${PROJECT_NAME} VTIL-Common VTIL-SymEx)
target_precompile_headers(${PROJECT_NAME} REUSE_FROM VTIL-Common)
if(VTIL_UNITY_BUILD)
set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD ON)
endif()

View file

@ -313,7 +313,7 @@ namespace vtil
//
if ( owner )
for ( auto& [vip, blk] : owner->explored_blocks )
fassert( blk != this );
fassert( blk.get() != this );
// Destroy instruction list.
//

View file

@ -0,0 +1,14 @@
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
// All rights reserved.
// Forward declarations for routine types to reduce header dependencies.
//
#pragma once
#include <cstdint>
namespace vtil
{
struct basic_block;
struct routine;
struct instruction;
using vip_t = uint64_t;
};

View file

@ -155,7 +155,7 @@ namespace vtil
auto it = explored_blocks.find( vip );
if ( it == explored_blocks.end() ) return nullptr;
return it->second;
return it->second.get();
}
basic_block* routine::get_block( vip_t vip ) const
{
@ -180,25 +180,26 @@ namespace vtil
// Try inserting into the map:
//
auto [it, inserted] = explored_blocks.emplace( vip, nullptr );
basic_block*& block = it->second;
if ( inserted )
{
// Create the block and set entry if none set.
//
block = new basic_block( this, vip );
if ( !entry_point ) entry_point = block;
it->second = std::make_unique<basic_block>( this, vip );
if ( !entry_point ) entry_point = it->second.get();
// Create self link.
//
path_cache[ block ][ block ].insert( block );
path_cache[ it->second.get() ][ it->second.get() ].insert( it->second.get() );
}
basic_block* block = it->second.get();
// Fix links and explore the path.
//
if ( src )
{
fassert( src->owner == this );
bool new_next = std::find( src->next.begin(), src->next.end(), block ) == src->next.end();
bool new_prev = inserted || std::find( block->prev.begin(), block->prev.end(), src ) == block->prev.end();
@ -261,10 +262,9 @@ namespace vtil
it++;
}
// Remove from explored blocks and delete it.
// Remove from explored blocks (unique_ptr automatically deletes the block).
//
explored_blocks.erase( block->entry_vip );
delete block;
}
// Gets a list of exits.
@ -280,7 +280,7 @@ namespace vtil
std::vector<const basic_block*> exits;
for ( auto& [vip, block] : explored_blocks )
if ( block->next.empty() )
exits.push_back( block );
exits.push_back( block.get() );
return exits;
}
@ -429,8 +429,8 @@ namespace vtil
// Sum up instructions in every block.
//
size_t n = 0;
for ( auto& [_, blk] : explored_blocks )
n += blk->size();
for ( auto& [_, blk_ptr] : explored_blocks )
n += blk_ptr->size();
return n;
}
size_t routine::num_branches() const
@ -442,8 +442,8 @@ namespace vtil
// Sum up paths in every block.
//
size_t n = 0;
for ( auto& [_, blk] : explored_blocks )
n += blk->next.size();
for ( auto& [_, blk_ptr] : explored_blocks )
n += blk_ptr->next.size();
return n;
}
@ -455,35 +455,39 @@ namespace vtil
//
std::lock_guard g{ this->mutex };
// Clear inter-block references before unique_ptrs auto-destruct,
// to avoid dangling pointers during destruction order.
//
for ( auto& [vip, block] : explored_blocks )
{
block->next.clear();
block->prev.clear();
delete std::exchange( block, nullptr );
}
explored_blocks.clear();
}
// Clones the routine and it's every block.
//
routine* routine::clone() const
std::unique_ptr<routine> routine::clone() const
{
// Acquire the routine mutex.
//
std::lock_guard g{ this->mutex };
// Copy the routine.
// Copy the routine (protected copy ctor -- explored_blocks is left empty).
//
auto copy = new routine( *this );
// Clone each block referenced.
auto copy = std::unique_ptr<routine>( new routine( *this ) );
// Deep-copy each block from the source routine.
//
for ( auto& [vip, block] : copy->explored_blocks )
for ( auto& [vip, block] : this->explored_blocks )
{
block = new basic_block( *block );
block->owner = copy;
auto cloned = std::make_unique<basic_block>( *block );
cloned->owner = copy.get();
copy->explored_blocks.emplace( vip, std::move( cloned ) );
}
// Fix block links.
// Fix block links (prev/next still point to source blocks, remap to copy's blocks).
//
for ( auto& [vip, block] : copy->explored_blocks )
for ( auto& list : { &block->next, &block->prev } )

View file

@ -60,9 +60,25 @@ namespace vtil
{
protected:
// This structure cannot be copied without a call to ::clone().
// Copy constructor must be explicit since explored_blocks contains unique_ptrs.
//
routine( const routine& ) = default;
routine& operator=( const routine& ) = default;
routine( const routine& o )
: mutex()
, arch_id( o.arch_id )
, explored_blocks() // NOT copied -- clone() handles block deep-copy
, path_cache() // NOT copied -- clone() rebuilds
, entry_point( nullptr )
, last_internal_id( o.last_internal_id.load() )
, routine_convention( o.routine_convention )
, subroutine_convention( o.subroutine_convention )
, spec_subroutine_conventions( o.spec_subroutine_conventions )
, local_opt_count( o.local_opt_count.load() )
, context( o.context )
, depth_ordered_list_cache{}
, cfg_epoch( o.cfg_epoch )
, epoch( o.epoch.load() )
{}
routine& operator=( const routine& ) = delete;
public:
// Mutex guarding the whole structure, more information on thread-safety can be found at basic_block.hpp.
//
@ -73,8 +89,9 @@ namespace vtil
architecture_identifier arch_id;
// Cache of explored blocks, mapping virtual instruction pointer to the basic block structure.
// Blocks are owned by the routine via unique_ptr for automatic lifetime management.
//
std::unordered_map<vip_t, basic_block*> explored_blocks;
std::unordered_map<vip_t, std::unique_ptr<basic_block>> explored_blocks;
// Cache of paths from block A to block B.
//
@ -183,7 +200,7 @@ namespace vtil
{
std::lock_guard _g( mutex );
for ( auto& [vip, block] : explored_blocks )
if ( enumerator::invoke( fn, block ).should_break )
if ( enumerator::invoke( fn, block.get() ).should_break )
return;
}
@ -268,6 +285,6 @@ namespace vtil
// Clones the routine and it's every block.
//
routine* clone() const;
std::unique_ptr<routine> clone() const;
};
};

View file

@ -92,7 +92,8 @@ namespace vtil
//
vip_t vip;
deserialize( in, vip );
blk = new basic_block( rtn, vip );
auto new_block = std::make_unique<basic_block>( rtn, vip );
blk = new_block.get();
deserialize( in, blk->sp_offset );
deserialize( in, blk->sp_index );
deserialize( in, blk->last_temporary_index );
@ -100,7 +101,7 @@ namespace vtil
deserialize( in, list );
blk->assign( list.begin(), list.end() );
blk->owner = rtn;
blk->owner->explored_blocks[ blk->entry_vip ] = blk;
rtn->explored_blocks[ blk->entry_vip ] = std::move( new_block );
// Read referenced VIP's.
//
@ -115,17 +116,17 @@ namespace vtil
{
// Reference the cached instance.
//
basic_block*& blk = rtn->explored_blocks[ vip ];
auto& blk_ptr = rtn->explored_blocks[ vip ];
// Keep reading next block until referenced block is found,
// once it is found break out of the loop and return the block.
//
while ( !blk )
while ( !blk_ptr )
{
basic_block* tmp;
deserialize( in, rtn, tmp );
}
return blk;
return blk_ptr.get();
};
std::transform( prev.begin(), prev.end(), std::back_inserter( blk->prev ), ref_resolve );
std::transform( next.begin(), next.end(), std::back_inserter( blk->next ), ref_resolve );
@ -161,7 +162,7 @@ namespace vtil
// Dump all blocks in cached order.
//
for ( auto& pair : rtn->explored_blocks )
serialize( out, pair.second );
serialize( out, pair.second.get() );
}
void deserialize( std::istream& in, routine*& rtn )
{
@ -176,7 +177,7 @@ namespace vtil
// Create a new routine.
//
rtn = new routine( hdr.arch_id );
rtn = new routine( hdr.arch_id ); // Caller takes ownership
// Read the entry point VIP.
//
@ -210,7 +211,7 @@ namespace vtil
// Assign the fetched entry point from cache and return.
//
rtn->entry_point = rtn->explored_blocks[ entry_vip ];
rtn->entry_point = rtn->explored_blocks[ entry_vip ].get();
if ( !rtn->entry_point )
throw std::runtime_error( "Failed resolving entry point." );

View file

@ -42,6 +42,10 @@ namespace vtil
//
struct tracer
{
// Virtual destructor for safe polymorphic deletion.
//
virtual ~tracer() = default;
inline static thread_local bool recursive_flag = false;
// Traces a variable across the basic block it belongs to and generates a symbolic expression

View file

@ -48,19 +48,25 @@ namespace vtil
{
// Reads from the register.
//
virtual symbolic::expression::reference read_register( const register_desc& desc ) const { unreachable(); return {}; }
// Virtual destructor for safe polymorphic deletion.
//
virtual ~vm_interface() = default;
// Reads from the register.
//
virtual symbolic::expression::reference read_register( const register_desc& desc ) const { logger::error( "Pure virtual call to vm_interface::read_register" ); }
// Reads the given number of bytes from the memory, returns null if aliasing fails.
//
virtual symbolic::expression::reference read_memory( const symbolic::expression::reference& pointer, size_t byte_count ) const { unreachable(); return {}; }
virtual symbolic::expression::reference read_memory( const symbolic::expression::reference& pointer, size_t byte_count ) const { logger::error( "Pure virtual call to vm_interface::read_memory" ); }
// Writes to the register.
//
virtual void write_register( const register_desc& desc,symbolic::expression::reference value ) { unreachable(); }
virtual void write_register( const register_desc& desc,symbolic::expression::reference value ) { logger::error( "Pure virtual call to vm_interface::write_register" ); }
// Writes the given expression to the memory, returns false if aliasing fails.
//
virtual bool write_memory( const symbolic::expression::reference& pointer, deferred_value<symbolic::expression::reference> value, bitcnt_t size ) { unreachable(); return false; }
virtual bool write_memory( const symbolic::expression::reference& pointer, deferred_value<symbolic::expression::reference> value, bitcnt_t size ) { logger::error( "Pure virtual call to vm_interface::write_memory" ); }
bool write_memory_v( const symbolic::expression::reference& pointer, symbolic::expression::reference value ) { return write_memory( pointer, std::move( value ), value.size() ); }
// Runs the given instruction, returns whether it was successful.

View file

@ -58,4 +58,32 @@ endif()
# Include Threads
find_package(Threads REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT})
target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT})
target_precompile_headers(${PROJECT_NAME} PUBLIC
<atomic>
<mutex>
<string>
<vector>
<unordered_map>
<unordered_set>
<type_traits>
<functional>
<optional>
<cstdint>
<memory>
<algorithm>
<tuple>
<array>
<chrono>
<string_view>
)
if(VTIL_UNITY_BUILD)
set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD ON)
endif()
if(VTIL_SANITIZE_THREADS AND CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
target_compile_options(${PROJECT_NAME} PUBLIC -fsanitize=thread)
target_link_options(${PROJECT_NAME} PUBLIC -fsanitize=thread)
endif()

View file

@ -71,11 +71,16 @@ namespace vtil
// Declare debug assertions, dassert is only asserted in debug mode, dassert_s
// has the same functionality but is still evaluated in release mode.
//
// Release-mode assertion: always active regardless of build configuration.
// Use for critical invariants that must never be violated (e.g., null checks after allocation).
//
#define release_assert(...) vtil::abort_if(!bool(__VA_ARGS__), fassert__stringify(__VA_ARGS__) " at " __FILE__ ":" fassert__istringify(__LINE__) )
#ifdef _DEBUG
#define dassert(...) fassert( __VA_ARGS__ )
#define dassert_s( ... ) fassert( __VA_ARGS__ )
#else
#define dassert(...)
#define dassert(...)
#define dassert_s( ... ) ( __VA_ARGS__ )
#endif

View file

@ -252,16 +252,17 @@ namespace vtil::logger
format::fix_parameter<params>( std::forward<params>( ps ) )...
);
// Try acquiring the lock.
// Try acquiring the lock. Intentionally prints even without lock
// for crash safety -- warnings during deadlocks must still be visible.
//
bool locked = logger_state.try_lock( 100ms );
// Print the warning.
//
set_color( CON_YLW );
fprintf( VTIL_LOGGER_ERR_DST, "\n[!] Warning: %s\n", message.c_str() );
// Unlock if previously locked.
// Release the lock if acquired.
//
if ( locked ) logger_state.unlock();
}
@ -296,6 +297,15 @@ namespace vtil::logger
set_color( CON_RED );
fprintf( VTIL_LOGGER_ERR_DST, "\n[*] Error: %s\n", message.c_str() );
// Print a minimal stack trace for post-mortem traceability.
//
#if defined(__GNUC__) || defined(__clang__)
{
fprintf( VTIL_LOGGER_ERR_DST, "[*] Stack trace:\n" );
fprintf( VTIL_LOGGER_ERR_DST, " [0] %p\n", __builtin_return_address( 0 ) );
}
#endif
// Break the program, leave the logger locked since we'll break anyways.
//
unreachable();
@ -320,7 +330,11 @@ namespace vtil::logger
fprintf( VTIL_LOGGER_ERR_DST, "\n[*] Error: %s\n", message.c_str() );
sleep_for( 1000ms );
}
catch ( ... ) {}
catch ( ... )
{
set_color( CON_RED );
fprintf( VTIL_LOGGER_ERR_DST, "\n[*] Error: Unknown non-std exception caught during terminate\n" );
}
// Call into previous handler if relevant.
//

View file

@ -67,21 +67,22 @@ namespace vtil
}
}
// Legacy SFINAE helper kept for reference; prefer requires clause below.
template<typename T, typename... params>
using enable_if_constructor = typename std::enable_if_t<should_invoke_constructor<T, params...>(), int>;
template<typename T>
inline static T* reloc_const( const T* ptr, const void* src, void* dst )
{
int64_t reloc_delta = ( int64_t ) dst - ( int64_t ) src;
return ( T* ) ( ( uint64_t ) ptr + reloc_delta );
int64_t reloc_delta = reinterpret_cast<intptr_t>( dst ) - reinterpret_cast<intptr_t>( src );
return reinterpret_cast<T*>( reinterpret_cast<uintptr_t>( ptr ) + reloc_delta );
}
template<typename T>
inline static T& reloc_const( const T& ref, const void* src, void* dst )
{
int64_t reloc_delta = ( int64_t ) dst - ( int64_t ) src;
return *( T* ) ( ( uint64_t ) &ref + reloc_delta );
int64_t reloc_delta = reinterpret_cast<intptr_t>( dst ) - reinterpret_cast<intptr_t>( src );
return *reinterpret_cast<T*>( reinterpret_cast<uintptr_t>( &ref ) + reloc_delta );
}
};
@ -97,29 +98,19 @@ namespace vtil
// Wrap atomic operations on reference counter.
//
// Atomic reference counting with consistent memory ordering across all platforms.
//
__forceinline static void inc_ref( object_entry* entry )
{
#ifdef _MSC_VER
std::atomic_fetch_add_explicit( &entry->second, +1, std::memory_order::relaxed );
#else
entry->second++;
#endif
}
__forceinline static bool dec_ref( object_entry* entry )
{
#ifdef _MSC_VER
return std::atomic_fetch_add_explicit( &entry->second, -1, std::memory_order::acq_rel ) == 1;
#else
return --entry->second == 0;
#endif
}
__forceinline static long get_ref( object_entry* entry )
{
#ifdef _MSC_VER
return std::atomic_load_explicit( &entry->second, std::memory_order::relaxed );
#else
return entry->second.load();
#endif
return std::atomic_load_explicit( &entry->second, std::memory_order::acquire );
}
// Store pointer as a 63-bit integer and append an additional bit to control temporary/allocated.
@ -145,7 +136,8 @@ namespace vtil
// Owning reference constructor.
//
template<typename... params, impl::enable_if_constructor<shared_reference<T>, params...> = 0>
template<typename... params>
requires ( impl::should_invoke_constructor<shared_reference<T>, params...>() )
shared_reference( params&&... p )
{
combined_value = ( uint64_t ) object_pool::construct
@ -241,13 +233,13 @@ namespace vtil
// Gets object itself.
//
constexpr const T* get() const { return ( const T* ) pointer; }
constexpr const T* get() const { return reinterpret_cast<const T*>( static_cast<uintptr_t>( pointer ) ); }
// Check if temporary pointer.
// - Micro optimized to generate cmp branch instead of bitmasked
// test since MSVC is too stupid apparently.
//
constexpr bool is_temporary() const { return ( ( int64_t ) combined_value ) < 0; /*return temporary;*/ }
constexpr bool is_temporary() const { return ( static_cast<int64_t>( combined_value ) ) < 0; /*return temporary;*/ }
// Converts to owning reference.
//
@ -271,7 +263,7 @@ namespace vtil
// Return the current pointer without const-qualifiers.
//
return ( T* ) combined_value;
return reinterpret_cast<T*>( combined_value );
}
// Simple validity checks.
@ -307,7 +299,7 @@ namespace vtil
T* operator+()
{
if ( is_temporary() ) [[unlikely]]
return ( T* ) pointer;
return reinterpret_cast<T*>( static_cast<uintptr_t>( pointer ) );
return own();
}
@ -377,7 +369,7 @@ namespace vtil
// Redirect pointer and dereferencing operator to the reference and cast to const-qualified equivalent.
//
constexpr const T* get() const { return ( const T* ) pointer; }
constexpr const T* get() const { return reinterpret_cast<const T*>( static_cast<uintptr_t>( pointer ) ); }
constexpr const T* operator->() const { return get(); }
constexpr const T& operator*() const { return *get(); }

View file

@ -28,6 +28,9 @@
#pragma once
#include "type_helpers.hpp"
#include "../io/asserts.hpp"
#ifdef _DEBUG
#include <typeinfo>
#endif
namespace vtil
{
@ -44,6 +47,9 @@ namespace vtil
void* obj = nullptr;
Ret( *fn )( void*, Args... ) = nullptr;
bool const_invocable = true;
#ifdef _DEBUG
const std::type_info* stored_type = nullptr;
#endif
// Null construction.
//
@ -61,6 +67,9 @@ namespace vtil
return ( *( F* ) obj )( std::forward<Args>( args )... );
};
const_invocable = Invocable<std::add_const_t<std::decay_t<F>>, Ret, Args...>;
#ifdef _DEBUG
stored_type = &typeid( F );
#endif
}
// Unsafe for storage.

View file

@ -120,6 +120,7 @@ namespace vtil
{
static_assert( alignof( object_entry ) <= 8, "Object aligned over max alignment." );
pool_instance* pool = ( pool_instance* ) malloc( sizeof( pool_instance ) + sizeof( object_entry ) * ( n - 1 ) );
release_assert( pool != nullptr );
pool->object_count = n;
return pool;
}

View file

@ -58,10 +58,14 @@ namespace vtil
using base_type = T;
using base_type::base_type;
// Allow copy/move construction and assignment, safety is left to the owner.
// Allow copy/move construction (creates a NEW unlocked mutex, does not transfer lock state).
// This is intentional: used by routine::clone() to create independent mutex for the copy.
//
relaxed_mutex( relaxed_mutex&& o ) {}
relaxed_mutex( const relaxed_mutex& o ) {}
// Assignment is a no-op (same rationale as copy/move ctors: mutex state is not transferable).
//
relaxed_mutex& operator=( relaxed_mutex&& o ) { return *this; }
relaxed_mutex& operator=( const relaxed_mutex& o ) { return *this; }
};

View file

@ -14,3 +14,9 @@ source_group(TREE ${PROJECT_SOURCE_DIR} FILES ${SOURCES} ${INCLUDES})
target_include_directories(${PROJECT_NAME} PUBLIC includes)
target_link_libraries(${PROJECT_NAME} VTIL-Common VTIL-SymEx VTIL-Architecture)
target_precompile_headers(${PROJECT_NAME} REUSE_FROM VTIL-Common)
if(VTIL_UNITY_BUILD)
set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD ON)
endif()

View file

@ -153,9 +153,9 @@ namespace vtil::optimizer
{
// Invoke parallel transformation.
//
transform_parallel( rtn->explored_blocks, [ & ] ( const std::pair<const vip_t, basic_block*>& pair )
transform_parallel( rtn->explored_blocks, [ & ] ( const std::pair<const vip_t, std::unique_ptr<basic_block>>& pair )
{
worker( pair.second );
worker( pair.second.get() );
} );
break;
}

View file

@ -48,10 +48,11 @@ namespace vtil::optimizer
//
cached_tracer local_tracer = {};
auto lbranch_info = aux::analyze_branch( blk, &local_tracer, {} );
ctracer.mtx.lock();
for ( auto& [k, v] : local_tracer.cache )
ctracer.cache[ k ] = v;
ctracer.mtx.unlock();
{
std::lock_guard _g{ ctracer.mtx };
for ( auto& [k, v] : local_tracer.cache )
ctracer.cache[ k ] = v;
}
auto branch_info = aux::analyze_branch( blk, &ctracer, { .cross_block = true, .pack = true, .resolve_opaque = true } );
// If branching to real, assert single next block.
@ -233,7 +234,7 @@ namespace vtil::optimizer
for ( auto it = rtn->explored_blocks.begin(); it != rtn->explored_blocks.end(); )
{
if ( it->second->prev.size() == 0 && it->second != rtn->entry_point )
if ( it->second->prev.size() == 0 && it->second.get() != rtn->entry_point )
{
// For each destination:
//
@ -241,14 +242,14 @@ namespace vtil::optimizer
{
// Remove the link.
//
block->prev.erase( std::remove( block->prev.begin(), block->prev.end(), it->second ), block->prev.end() );
block->prev.erase( std::remove( block->prev.begin(), block->prev.end(), it->second.get() ), block->prev.end() );
// If no prev link left, repeat logic.
//
repeat |= block->prev.empty();
}
// Erase block.
// Erase block (unique_ptr auto-deletes).
//
it = rtn->explored_blocks.erase( it );
}

View file

@ -42,7 +42,7 @@ namespace vtil::optimizer::validation
deserialize( iss, rtn );
return rtn;
}();
return std::unique_ptr<routine>{ cache->clone() };
return cache->clone();
}
bool test1::validate( const routine* rtn ) const

View file

@ -18,4 +18,10 @@ target_link_libraries(${PROJECT_NAME} VTIL-Common)
if(MSVC)
# /bigobj for reasons
target_compile_options(${PROJECT_NAME} PRIVATE /bigobj)
endif()
target_precompile_headers(${PROJECT_NAME} REUSE_FROM VTIL-Common)
if(VTIL_UNITY_BUILD)
set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD ON)
endif()

View file

@ -87,24 +87,5 @@ namespace vtil::symbolic::directive
return true;
}
// Simple copyable unique pointer implementation.
//
instance::reference::reference( const instance& o ) : ptr( new instance( o ) ) {}
instance::reference::reference( instance&& o ) : ptr( new instance( std::move( o ) ) ) {}
instance::reference::reference( const reference& o ) : ptr( o ? new instance( *o ) : nullptr ) {}
instance::reference::reference( reference&& o ) : ptr( std::exchange( o.ptr, nullptr ) ) {}
instance::reference::~reference()
{
if ( ptr ) delete ptr;
}
instance::reference& instance::reference::operator=( instance::reference&& o )
{
ptr = std::exchange( o.ptr, nullptr );
return *this;
}
instance::reference& instance::reference::operator=( const instance::reference& o )
{
ptr = o ? new instance( *o ) : nullptr;
return *this;
}
// Note: instance::reference is now fully defined inline in directive.hpp :))
};

View file

@ -148,41 +148,45 @@ namespace vtil::symbolic::directive
//
struct instance : math::operable<instance>
{
// Simple copyable unique pointer implementation.
// Deep-copying unique pointer for directive instances.
//
struct reference
{
instance* ptr = nullptr;
std::unique_ptr<instance> ptr;
// Construct by implicit null or instance value.
//
reference() {}
reference( const instance& i );
reference( instance&& i );
reference() = default;
reference( const instance& i ) : ptr( std::make_unique<instance>( i ) ) {}
reference( instance&& i ) : ptr( std::make_unique<instance>( std::move( i ) ) ) {}
// Copy / Move from another reference.
// Deep-copy from another reference, move is default.
//
reference( const reference& o );
reference( reference&& o );
reference& operator=( reference&& o );
reference& operator=( const reference& o );
reference( const reference& o ) : ptr( o.ptr ? std::make_unique<instance>( *o.ptr ) : nullptr ) {}
reference( reference&& ) = default;
reference& operator=( reference&& ) = default;
reference& operator=( const reference& o )
{
ptr = o.ptr ? std::make_unique<instance>( *o.ptr ) : nullptr;
return *this;
}
// Destructor deletes the value.
// Default destructor.
//
~reference();
~reference() = default;
// Null check.
//
explicit operator bool() const { return ptr; }
explicit operator bool() const { return ptr != nullptr; }
// Pointer interface.
//
operator instance*() { return ptr; }
operator const instance*() const { return ptr; }
operator instance*() { return ptr.get(); }
operator const instance*() const { return ptr.get(); }
instance& operator*() { return *ptr; }
const instance& operator*() const { return *ptr; }
instance* operator->() { return ptr; }
const instance* operator->() const { return ptr; }
instance* operator->() { return ptr.get(); }
const instance* operator->() const { return ptr.get(); }
};
// If symbolic variable, the identifier of the variable

View file

@ -83,8 +83,8 @@ namespace vtil::symbolic
expression::reference lhs, rhs;
std::array tx = {
std::pair( &lhs, dir->lhs.ptr ),
std::pair( &rhs, dir->rhs.ptr )
std::pair( &lhs, dir->lhs.ptr.get() ),
std::pair( &rhs, dir->rhs.ptr.get() )
};
if ( tx[ 1 ].second->priority > tx[ 0 ].second->priority )

View file

@ -0,0 +1,11 @@
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
// All rights reserved.
// Forward declarations for expression types to reduce header dependencies.
//
#pragma once
namespace vtil::symbolic
{
struct expression;
struct unique_identifier;
};

View file

@ -1,3 +1,9 @@
#if defined(__APPLE__) && (defined(__aarch64__) || defined(__arm64__))
#ifndef DOCTEST_BREAK_INTO_DEBUGGER
#define DOCTEST_BREAK_INTO_DEBUGGER() __builtin_debugtrap()
#endif
#endif
#include "doctest.h"
#include <vtil/vtil>
#include <vtil/arch>

View file

@ -3,5 +3,11 @@
#define DOCTEST_CONFIG_NO_POSIX_SIGNALS
#endif
#if defined(__APPLE__) && (defined(__aarch64__) || defined(__arm64__))
#ifndef DOCTEST_BREAK_INTO_DEBUGGER
#define DOCTEST_BREAK_INTO_DEBUGGER() __builtin_debugtrap()
#endif
#endif
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"