mirror of
https://github.com/darlinghq/darling-objc4.git
synced 2024-11-23 04:09:46 +00:00
270 lines
8.6 KiB
Plaintext
270 lines
8.6 KiB
Plaintext
/*
|
|
* Copyright (c) 2004-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@
|
|
*/
|
|
/*
|
|
Implementation of the weak / associative references for non-GC mode.
|
|
*/
|
|
|
|
|
|
#include "objc-private.h"
|
|
#include <objc/message.h>
|
|
#include <map>
|
|
#include "DenseMapExtras.h"
|
|
|
|
// expanded policy bits.
|
|
|
|
enum {
|
|
OBJC_ASSOCIATION_SETTER_ASSIGN = 0,
|
|
OBJC_ASSOCIATION_SETTER_RETAIN = 1,
|
|
OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below.
|
|
OBJC_ASSOCIATION_GETTER_READ = (0 << 8),
|
|
OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8),
|
|
OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8),
|
|
OBJC_ASSOCIATION_SYSTEM_OBJECT = _OBJC_ASSOCIATION_SYSTEM_OBJECT, // 1 << 16
|
|
};
|
|
|
|
spinlock_t AssociationsManagerLock;
|
|
|
|
namespace objc {
|
|
|
|
class ObjcAssociation {
|
|
uintptr_t _policy;
|
|
id _value;
|
|
public:
|
|
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
|
|
ObjcAssociation() : _policy(0), _value(nil) {}
|
|
ObjcAssociation(const ObjcAssociation &other) = default;
|
|
ObjcAssociation &operator=(const ObjcAssociation &other) = default;
|
|
ObjcAssociation(ObjcAssociation &&other) : ObjcAssociation() {
|
|
swap(other);
|
|
}
|
|
|
|
inline void swap(ObjcAssociation &other) {
|
|
std::swap(_policy, other._policy);
|
|
std::swap(_value, other._value);
|
|
}
|
|
|
|
inline uintptr_t policy() const { return _policy; }
|
|
inline id value() const { return _value; }
|
|
|
|
inline void acquireValue() {
|
|
if (_value) {
|
|
switch (_policy & 0xFF) {
|
|
case OBJC_ASSOCIATION_SETTER_RETAIN:
|
|
_value = objc_retain(_value);
|
|
break;
|
|
case OBJC_ASSOCIATION_SETTER_COPY:
|
|
_value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void releaseHeldValue() {
|
|
if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
|
|
objc_release(_value);
|
|
}
|
|
}
|
|
|
|
inline void retainReturnedValue() {
|
|
if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
|
|
objc_retain(_value);
|
|
}
|
|
}
|
|
|
|
inline id autoreleaseReturnedValue() {
|
|
if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
|
|
return objc_autorelease(_value);
|
|
}
|
|
return _value;
|
|
}
|
|
};
|
|
|
|
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
|
|
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
|
|
|
|
// class AssociationsManager manages a lock / hash table singleton pair.
|
|
// Allocating an instance acquires the lock
|
|
|
|
class AssociationsManager {
|
|
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
|
|
static Storage _mapStorage;
|
|
|
|
public:
|
|
AssociationsManager() { AssociationsManagerLock.lock(); }
|
|
~AssociationsManager() { AssociationsManagerLock.unlock(); }
|
|
|
|
AssociationsHashMap &get() {
|
|
return _mapStorage.get();
|
|
}
|
|
|
|
static void init() {
|
|
_mapStorage.init();
|
|
}
|
|
};
|
|
|
|
AssociationsManager::Storage AssociationsManager::_mapStorage;
|
|
|
|
} // namespace objc
|
|
|
|
using namespace objc;
|
|
|
|
void
|
|
_objc_associations_init()
|
|
{
|
|
AssociationsManager::init();
|
|
}
|
|
|
|
id
|
|
_object_get_associative_reference(id object, const void *key)
|
|
{
|
|
ObjcAssociation association{};
|
|
|
|
{
|
|
AssociationsManager manager;
|
|
AssociationsHashMap &associations(manager.get());
|
|
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
|
|
if (i != associations.end()) {
|
|
ObjectAssociationMap &refs = i->second;
|
|
ObjectAssociationMap::iterator j = refs.find(key);
|
|
if (j != refs.end()) {
|
|
association = j->second;
|
|
association.retainReturnedValue();
|
|
}
|
|
}
|
|
}
|
|
|
|
return association.autoreleaseReturnedValue();
|
|
}
|
|
|
|
void
|
|
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
|
|
{
|
|
// This code used to work when nil was passed for object and key. Some code
|
|
// probably relies on that to not crash. Check and handle it explicitly.
|
|
// rdar://problem/44094390
|
|
if (!object && !value) return;
|
|
|
|
if (object->getIsa()->forbidsAssociatedObjects())
|
|
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
|
|
|
|
DisguisedPtr<objc_object> disguised{(objc_object *)object};
|
|
ObjcAssociation association{policy, value};
|
|
|
|
// retain the new value (if any) outside the lock.
|
|
association.acquireValue();
|
|
|
|
bool isFirstAssociation = false;
|
|
{
|
|
AssociationsManager manager;
|
|
AssociationsHashMap &associations(manager.get());
|
|
|
|
if (value) {
|
|
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
|
|
if (refs_result.second) {
|
|
/* it's the first association we make */
|
|
isFirstAssociation = true;
|
|
}
|
|
|
|
/* establish or replace the association */
|
|
auto &refs = refs_result.first->second;
|
|
auto result = refs.try_emplace(key, std::move(association));
|
|
if (!result.second) {
|
|
association.swap(result.first->second);
|
|
}
|
|
} else {
|
|
auto refs_it = associations.find(disguised);
|
|
if (refs_it != associations.end()) {
|
|
auto &refs = refs_it->second;
|
|
auto it = refs.find(key);
|
|
if (it != refs.end()) {
|
|
association.swap(it->second);
|
|
refs.erase(it);
|
|
if (refs.size() == 0) {
|
|
associations.erase(refs_it);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Call setHasAssociatedObjects outside the lock, since this
|
|
// will call the object's _noteAssociatedObjects method if it
|
|
// has one, and this may trigger +initialize which might do
|
|
// arbitrary stuff, including setting more associated objects.
|
|
if (isFirstAssociation)
|
|
object->setHasAssociatedObjects();
|
|
|
|
// release the old value (outside of the lock).
|
|
association.releaseHeldValue();
|
|
}
|
|
|
|
// Unlike setting/getting an associated reference,
|
|
// this function is performance sensitive because of
|
|
// raw isa objects (such as OS Objects) that can't track
|
|
// whether they have associated objects.
|
|
void
|
|
_object_remove_assocations(id object, bool deallocating)
|
|
{
|
|
ObjectAssociationMap refs{};
|
|
|
|
{
|
|
AssociationsManager manager;
|
|
AssociationsHashMap &associations(manager.get());
|
|
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
|
|
if (i != associations.end()) {
|
|
refs.swap(i->second);
|
|
|
|
// If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
|
|
bool didReInsert = false;
|
|
if (!deallocating) {
|
|
for (auto &ref: refs) {
|
|
if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
|
|
i->second.insert(ref);
|
|
didReInsert = true;
|
|
}
|
|
}
|
|
}
|
|
if (!didReInsert)
|
|
associations.erase(i);
|
|
}
|
|
}
|
|
|
|
// Associations to be released after the normal ones.
|
|
SmallVector<ObjcAssociation *, 4> laterRefs;
|
|
|
|
// release everything (outside of the lock).
|
|
for (auto &i: refs) {
|
|
if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
|
|
// If we are not deallocating, then RELEASE_LATER associations don't get released.
|
|
if (deallocating)
|
|
laterRefs.append(&i.second);
|
|
} else {
|
|
i.second.releaseHeldValue();
|
|
}
|
|
}
|
|
for (auto *later: laterRefs) {
|
|
later->releaseHeldValue();
|
|
}
|
|
}
|