com 0.6.0

Utilities for implementing COM Client and Servers
Documentation
//! Runtime support for reference counting.
//!
//! This is part of the implementation of `com-rs`, and should not be used
//! directly by application code. It is used by code generated by the
//! `com::class!` macro.

use core::sync::atomic::{AtomicU32, Ordering};

// We check for u32::MAX / 2, instead of u32::MAX, to guard against AddRef attacks.
const REFCOUNT_OVERFLOW_MAX: u32 = u32::MAX / 2;

/// Implements `IUnknown::AddRef` for COM servers.
///
/// Increments the reference count and returns the new reference count.
///
/// We do our best to harden code against `AddRef` attacks. An `AddRef` attack
/// is one that intentionally overflows a reference count, so that a `Release`
/// call can be used to destroy an object, even though other references are
/// still outstanding.
#[doc(hidden)]
#[inline(always)]
pub fn addref(refcount: &AtomicU32) -> u32 {
    let old_refcount = refcount.fetch_add(1, Ordering::SeqCst);
    if old_refcount >= REFCOUNT_OVERFLOW_MAX {
        // Undo the increment that we just performed.
        let _ = refcount.fetch_sub(1, Ordering::SeqCst);
        addref_overflowed();
    } else {
        old_refcount + 1
    }
}

/// Implements `IUnknown::Release` for COM servers.
///
/// Decrements the reference count and returns the new reference count. The
/// caller must check whether the return value is zero, and if so, should
/// destroy the COM server.
#[doc(hidden)]
#[inline(always)]
pub fn release(refcount: &AtomicU32) -> u32 {
    let old_refcount = refcount.fetch_sub(1, Ordering::SeqCst);
    if old_refcount == 0 {
        // The reference count was invalid.
        // In safe Rust, this should be impossible.
        // Of course, other clients outside of safe Rust can use COM.
        release_underflowed();
    } else {
        old_refcount - 1
    }
}

/// Panics, because an `IUnknown::AddRef()` call has overflowed.
///
/// This function is never inlined, so it keeps the (some what verbose)
/// machinery of calling the panic handler out of mainline code, which is
/// inlined in many places. This also gives us a very convenient call stack
/// in a debugger.
#[inline(never)]
fn addref_overflowed() -> ! {
    panic!("IUnknown::AddRef: refcount has overflowed");
}

/// Panics, because an `IUnknown::Release()` call has underflowed.
///
/// This function is never inlined, so it keeps the (some what verbose)
/// machinery of calling the panic handler out of mainline code, which is
/// inlined in many places. This also gives us a very convenient call stack
/// in a debugger.
#[doc(hidden)]
#[inline(never)]
fn release_underflowed() -> ! {
    panic!("IUnknown::Release called, but refcount was zero");
}