objc-lockdebug.mm   [plain text]


/*
 * Copyright (c) 2007 Apple Inc.  All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/***********************************************************************
* objc-lock.m
* Error-checking locks for debugging.
**********************************************************************/

#include "objc-private.h"

#if LOCKDEBUG  &&  !TARGET_OS_WIN32

#include <unordered_map>


/***********************************************************************
* Thread-local bool set during _objc_atfork_prepare().
* That function is allowed to break some lock ordering rules.
**********************************************************************/

static tls_key_t fork_prepare_tls;

void
lockdebug_setInForkPrepare(bool inForkPrepare)
{
    INIT_ONCE_PTR(fork_prepare_tls, tls_create(nil), (void)0);
    tls_set(fork_prepare_tls, (void*)inForkPrepare);
}

static bool
inForkPrepare()
{
    INIT_ONCE_PTR(fork_prepare_tls, tls_create(nil), (void)0);
    return (bool)tls_get(fork_prepare_tls);
}



/***********************************************************************
* Lock order graph.
* "lock X precedes lock Y" means that X must be acquired first.
* This property is transitive.
**********************************************************************/

struct lockorder {
    const void *l;
    std::vector<const lockorder *> predecessors;

    mutable std::unordered_map<const lockorder *, bool> memo;

    lockorder(const void *newl) : l(newl) { }
};

static std::unordered_map<const void*, lockorder *> lockOrderList;
// not mutex_t because we don't want lock debugging on this lock
static mutex_tt<false> lockOrderLock;

static bool 
lockPrecedesLock(const lockorder *oldlock, const lockorder *newlock)
{
    auto memoed = newlock->memo.find(oldlock);
    if (memoed != newlock->memo.end()) {
        return memoed->second;
    }

    bool result = false;
    for (const auto *pre : newlock->predecessors) {
        if (oldlock == pre  ||  lockPrecedesLock(oldlock, pre)) {
            result = true;
            break;
        }
    }

    newlock->memo[oldlock] = result;
    return result;
}

static bool 
lockPrecedesLock(const void *oldlock, const void *newlock)
{
    mutex_tt<false>::locker lock(lockOrderLock);

    auto oldorder = lockOrderList.find(oldlock);
    auto neworder = lockOrderList.find(newlock);
    if (neworder == lockOrderList.end() || oldorder == lockOrderList.end()) {
        return false;
    }
    return lockPrecedesLock(oldorder->second, neworder->second);
}

static bool
lockUnorderedWithLock(const void *oldlock, const void *newlock)
{
    mutex_tt<false>::locker lock(lockOrderLock);
    
    auto oldorder = lockOrderList.find(oldlock);
    auto neworder = lockOrderList.find(newlock);
    if (neworder == lockOrderList.end() || oldorder == lockOrderList.end()) {
        return true;
    }
    
    if (lockPrecedesLock(oldorder->second, neworder->second) ||
        lockPrecedesLock(neworder->second, oldorder->second))
    {
        return false;
    }

    return true;
}

void lockdebug_lock_precedes_lock(const void *oldlock, const void *newlock)
{
    if (lockPrecedesLock(newlock, oldlock)) {
        _objc_fatal("contradiction in lock order declaration");
    }

    mutex_tt<false>::locker lock(lockOrderLock);

    auto oldorder = lockOrderList.find(oldlock);
    auto neworder = lockOrderList.find(newlock);
    if (oldorder == lockOrderList.end()) {
        lockOrderList[oldlock] = new lockorder(oldlock);
        oldorder = lockOrderList.find(oldlock);
    }
    if (neworder == lockOrderList.end()) {
        lockOrderList[newlock] = new lockorder(newlock);
        neworder = lockOrderList.find(newlock);
    }

    neworder->second->predecessors.push_back(oldorder->second);
}


/***********************************************************************
* Recording - per-thread list of mutexes and monitors held
**********************************************************************/

enum class lockkind {
    MUTEX = 1, MONITOR = 2, RDLOCK = 3, WRLOCK = 4, RECURSIVE = 5
};

#define MUTEX     lockkind::MUTEX
#define MONITOR   lockkind::MONITOR
#define RDLOCK    lockkind::RDLOCK
#define WRLOCK    lockkind::WRLOCK
#define RECURSIVE lockkind::RECURSIVE

struct lockcount {
    lockkind k;  // the kind of lock it is (MUTEX, MONITOR, etc)
    int i;       // the lock's nest count
};

using objc_lock_list = std::unordered_map<const void *, lockcount>;


// Thread-local list of locks owned by a thread.
// Used by lock ownership checks.
static tls_key_t lock_tls;

// Global list of all locks.
// Used by fork() safety check.
// This can't be a static struct because of C++ initialization order problems.
static objc_lock_list& AllLocks() {
    static objc_lock_list *locks;
    INIT_ONCE_PTR(locks, new objc_lock_list, (void)0);
    return *locks;
}


static void
destroyLocks(void *value)
{
    auto locks = (objc_lock_list *)value;
    // fixme complain about any still-held locks?
    if (locks) delete locks;
}

static objc_lock_list&
ownedLocks()
{
    // Use a dedicated tls key to prevent differences vs non-debug in 
    // usage of objc's other tls keys (required for some unit tests).
    INIT_ONCE_PTR(lock_tls, tls_create(&destroyLocks), (void)0);

    auto locks = (objc_lock_list *)tls_get(lock_tls);
    if (!locks) {
        locks = new objc_lock_list;
        tls_set(lock_tls, locks);
    }

    return *locks;
}

static bool 
hasLock(objc_lock_list& locks, const void *lock, lockkind kind)
{
    auto iter = locks.find(lock);
    if (iter != locks.end() && iter->second.k == kind) return true;
    return false;
}


static const char *sym(const void *lock)
{
    Dl_info info;
    int ok = dladdr(lock, &info);
    if (ok && info.dli_sname && info.dli_sname[0]) return info.dli_sname;
    else return "??";
}

static void 
setLock(objc_lock_list& locks, const void *lock, lockkind kind)
{
    // Check if we already own this lock.
    auto iter = locks.find(lock);
    if (iter != locks.end() && iter->second.k == kind) {
        iter->second.i++;
        return;
    }

    // Newly-acquired lock. Verify lock ordering.
    // Locks not in AllLocks are exempt (i.e. @synchronize locks)
    if (&locks != &AllLocks() && AllLocks().find(lock) != AllLocks().end()) {
        for (auto& oldlock : locks) {
            if (AllLocks().find(oldlock.first) == AllLocks().end()) {
                // oldlock is exempt
                continue;
            }

            if (lockPrecedesLock(lock, oldlock.first)) {
                _objc_fatal("lock %p (%s) incorrectly acquired before %p (%s)",
                            oldlock.first, sym(oldlock.first), lock, sym(lock));
            }
            if (!inForkPrepare() &&
                lockUnorderedWithLock(lock, oldlock.first))
            {
                // _objc_atfork_prepare is allowed to acquire
                // otherwise-unordered locks, but nothing else may.
                _objc_fatal("lock %p (%s) acquired before %p (%s) "
                            "with no defined lock order",
                            oldlock.first, sym(oldlock.first), lock, sym(lock));
            }
        }
    }

    locks[lock] = lockcount{kind, 1};
}

static void 
clearLock(objc_lock_list& locks, const void *lock, lockkind kind)
{
    auto iter = locks.find(lock);
    if (iter != locks.end()) {
        auto& l = iter->second;
        if (l.k == kind) {
            if (--l.i == 0) {
                locks.erase(iter);
            }
            return;
        }
    }

    _objc_fatal("lock not found!");
}


/***********************************************************************
* fork() safety checking
**********************************************************************/

void
lockdebug_remember_mutex(mutex_t *lock)
{
    setLock(AllLocks(), lock, MUTEX);
}

void
lockdebug_remember_recursive_mutex(recursive_mutex_t *lock)
{
    setLock(AllLocks(), lock, RECURSIVE);
}

void
lockdebug_remember_monitor(monitor_t *lock)
{
    setLock(AllLocks(), lock, MONITOR);
}

void
lockdebug_assert_all_locks_locked()
{
    auto& owned = ownedLocks();

    for (const auto& l : AllLocks()) {
        if (!hasLock(owned, l.first, l.second.k)) {
            _objc_fatal("lock %p:%d is incorrectly not owned",
                        l.first, l.second.k);
        }
    }
}

void
lockdebug_assert_no_locks_locked()
{
    auto& owned = ownedLocks();

    for (const auto& l : AllLocks()) {
        if (hasLock(owned, l.first, l.second.k)) {
            _objc_fatal("lock %p:%d is incorrectly owned", l.first, l.second.k);
        }
    }
}


/***********************************************************************
* Mutex checking
**********************************************************************/

void 
lockdebug_mutex_lock(mutex_t *lock)
{
    auto& locks = ownedLocks();
    
    if (hasLock(locks, lock, MUTEX)) {
        _objc_fatal("deadlock: relocking mutex");
    }
    setLock(locks, lock, MUTEX);
}

// try-lock success is the only case with lockdebug effects.
// try-lock when already locked is OK (will fail)
// try-lock failure does nothing.
void 
lockdebug_mutex_try_lock_success(mutex_t *lock)
{
    auto& locks = ownedLocks();
    setLock(locks, lock, MUTEX);
}

void 
lockdebug_mutex_unlock(mutex_t *lock)
{
    auto& locks = ownedLocks();

    if (!hasLock(locks, lock, MUTEX)) {
        _objc_fatal("unlocking unowned mutex");
    }
    clearLock(locks, lock, MUTEX);
}


void 
lockdebug_mutex_assert_locked(mutex_t *lock)
{
    auto& locks = ownedLocks();

    if (!hasLock(locks, lock, MUTEX)) {
        _objc_fatal("mutex incorrectly not locked");
    }
}

void 
lockdebug_mutex_assert_unlocked(mutex_t *lock)
{
    auto& locks = ownedLocks();

    if (hasLock(locks, lock, MUTEX)) {
        _objc_fatal("mutex incorrectly locked");
    }
}


/***********************************************************************
* Recursive mutex checking
**********************************************************************/

void 
lockdebug_recursive_mutex_lock(recursive_mutex_t *lock)
{
    auto& locks = ownedLocks();
    setLock(locks, lock, RECURSIVE);
}

void 
lockdebug_recursive_mutex_unlock(recursive_mutex_t *lock)
{
    auto& locks = ownedLocks();

    if (!hasLock(locks, lock, RECURSIVE)) {
        _objc_fatal("unlocking unowned recursive mutex");
    }
    clearLock(locks, lock, RECURSIVE);
}


void 
lockdebug_recursive_mutex_assert_locked(recursive_mutex_t *lock)
{
    auto& locks = ownedLocks();

    if (!hasLock(locks, lock, RECURSIVE)) {
        _objc_fatal("recursive mutex incorrectly not locked");
    }
}

void 
lockdebug_recursive_mutex_assert_unlocked(recursive_mutex_t *lock)
{
    auto& locks = ownedLocks();

    if (hasLock(locks, lock, RECURSIVE)) {
        _objc_fatal("recursive mutex incorrectly locked");
    }
}


/***********************************************************************
* Monitor checking
**********************************************************************/

void 
lockdebug_monitor_enter(monitor_t *lock)
{
    auto& locks = ownedLocks();

    if (hasLock(locks, lock, MONITOR)) {
        _objc_fatal("deadlock: relocking monitor");
    }
    setLock(locks, lock, MONITOR);
}

void 
lockdebug_monitor_leave(monitor_t *lock)
{
    auto& locks = ownedLocks();

    if (!hasLock(locks, lock, MONITOR)) {
        _objc_fatal("unlocking unowned monitor");
    }
    clearLock(locks, lock, MONITOR);
}

void 
lockdebug_monitor_wait(monitor_t *lock)
{
    auto& locks = ownedLocks();

    if (!hasLock(locks, lock, MONITOR)) {
        _objc_fatal("waiting in unowned monitor");
    }
}


void 
lockdebug_monitor_assert_locked(monitor_t *lock)
{
    auto& locks = ownedLocks();

    if (!hasLock(locks, lock, MONITOR)) {
        _objc_fatal("monitor incorrectly not locked");
    }
}

void 
lockdebug_monitor_assert_unlocked(monitor_t *lock)
{
    auto& locks = ownedLocks();

    if (hasLock(locks, lock, MONITOR)) {
        _objc_fatal("monitor incorrectly held");
    }
}

#endif