#include "Block_private.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <dlfcn.h>
#if !TARGET_OS_WIN32
#include <libkern/OSAtomic.h>
#else
#define _CRT_SECURE_NO_WARNINGS 1
#include <windows.h>
static __inline bool OSAtomicCompareAndSwapLong(long oldl, long newl, long volatile *dst)
{
long original = InterlockedCompareExchange(dst, newl, oldl);
return (original == oldl);
}
static __inline bool OSAtomicCompareAndSwapInt(int oldi, int newi, int volatile *dst)
{
int original = InterlockedCompareExchange(dst, newi, oldi);
return (original == oldi);
}
#endif
static void *_Block_copy_class = _NSConcreteMallocBlock;
static void *_Block_copy_finalizing_class = _NSConcreteMallocBlock;
static int _Block_copy_flag = BLOCK_NEEDS_FREE;
static int _Byref_flag_initial_value = BLOCK_NEEDS_FREE | 4;
static const int WANTS_ONE = (1 << 16);
static bool isGC = false;
static int latching_incr_int(int *where) {
while (1) {
int old_value = *(volatile int *)where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return BLOCK_REFCOUNT_MASK;
}
if (OSAtomicCompareAndSwapInt(old_value, old_value+2, (volatile int *)where)) {
return old_value+2;
}
}
}
static bool latching_incr_int_not_deallocating(int *where) {
while (1) {
int old_value = *(volatile int *)where;
if (old_value & 1) {
return false;
}
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return true;
}
if (OSAtomicCompareAndSwapInt(old_value, old_value+2, (volatile int *)where)) {
return true;
}
}
}
static bool latching_decr_int_should_deallocate(int *where) {
while (1) {
int old_value = *(volatile int *)where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return false; }
if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
return false; }
int new_value = old_value - 2;
bool result = false;
if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) {
new_value = old_value - 1;
result = true;
}
if (OSAtomicCompareAndSwapInt(old_value, new_value, (volatile int *)where)) {
return result;
}
}
}
static bool latching_decr_int_now_zero(int *where) {
while (1) {
int old_value = *(volatile int *)where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return false; }
if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
return false; }
int new_value = old_value - 2;
if (OSAtomicCompareAndSwapInt(old_value, new_value, (volatile int *)where)) {
return (new_value & BLOCK_REFCOUNT_MASK) == 0;
}
}
}
#if !TARGET_OS_WIN32
#pragma mark GC Support Routines
#endif
static void *_Block_alloc_default(const unsigned long size, const bool initialCountIsOne __unused, const bool isObject __unused) {
return malloc(size);
}
static void _Block_assign_default(void *value, void **destptr) {
*destptr = value;
}
static void _Block_setHasRefcount_default(const void *ptr __unused, const bool hasRefcount __unused) {
}
static void _Block_do_nothing(const void *aBlock __unused) { }
static void _Block_retain_object_default(const void *ptr __unused) {
}
static void _Block_release_object_default(const void *ptr __unused) {
}
static void _Block_assign_weak_default(const void *ptr, void *dest) {
#if !TARGET_OS_WIN32
*(long *)dest = (long)ptr;
#else
*(void **)dest = (void *)ptr;
#endif
}
static void _Block_memmove_default(void *dst, void *src, unsigned long size) {
memmove(dst, src, (size_t)size);
}
static void _Block_memmove_gc_broken(void *dest, void *src, unsigned long size) {
void **destp = (void **)dest;
void **srcp = (void **)src;
while (size) {
_Block_assign_default(*srcp, destp);
destp++;
srcp++;
size -= sizeof(void *);
}
}
static void _Block_destructInstance_default(const void *aBlock __unused) {}
static void *(*_Block_allocator)(const unsigned long, const bool isOne, const bool isObject) = _Block_alloc_default;
static void (*_Block_deallocator)(const void *) = (void (*)(const void *))free;
static void (*_Block_assign)(void *value, void **destptr) = _Block_assign_default;
static void (*_Block_setHasRefcount)(const void *ptr, const bool hasRefcount) = _Block_setHasRefcount_default;
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
static void (*_Block_release_object)(const void *ptr) = _Block_release_object_default;
static void (*_Block_assign_weak)(const void *dest, void *ptr) = _Block_assign_weak_default;
static void (*_Block_memmove)(void *dest, void *src, unsigned long size) = _Block_memmove_default;
static void (*_Block_destructInstance) (const void *aBlock) = _Block_destructInstance_default;
void _Block_use_GC( void *(*alloc)(const unsigned long, const bool isOne, const bool isObject),
void (*setHasRefcount)(const void *, const bool),
void (*gc_assign)(void *, void **),
void (*gc_assign_weak)(const void *, void *),
void (*gc_memmove)(void *, void *, unsigned long)) {
isGC = true;
_Block_allocator = alloc;
_Block_deallocator = _Block_do_nothing;
_Block_assign = gc_assign;
_Block_copy_flag = BLOCK_IS_GC;
_Block_copy_class = _NSConcreteAutoBlock;
_Block_copy_finalizing_class = _NSConcreteFinalizingBlock;
_Block_setHasRefcount = setHasRefcount;
_Byref_flag_initial_value = BLOCK_IS_GC; _Block_retain_object = _Block_do_nothing;
_Block_release_object = _Block_do_nothing;
_Block_assign_weak = gc_assign_weak;
_Block_memmove = gc_memmove;
}
void _Block_use_GC5( void *(*alloc)(const unsigned long, const bool isOne, const bool isObject),
void (*setHasRefcount)(const void *, const bool),
void (*gc_assign)(void *, void **),
void (*gc_assign_weak)(const void *, void *)) {
_Block_use_GC(alloc, setHasRefcount, gc_assign, gc_assign_weak, _Block_memmove_gc_broken);
}
void _Block_use_RR( void (*retain)(const void *),
void (*release)(const void *)) {
_Block_retain_object = retain;
_Block_release_object = release;
_Block_destructInstance = dlsym(RTLD_DEFAULT, "objc_destructInstance");
}
void _Block_use_RR2(const Block_callbacks_RR *callbacks) {
_Block_retain_object = callbacks->retain;
_Block_release_object = callbacks->release;
_Block_destructInstance = callbacks->destructInstance;
}
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;
}
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
static __inline bool _Block_has_layout(struct Block_layout *aBlock) {
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return false;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return ((struct Block_descriptor_3 *)desc)->layout != NULL;
}
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
(*desc->copy)(result, aBlock); }
static void _Block_call_dispose_helper(struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
(*desc->dispose)(aBlock);
}
#if !TARGET_OS_WIN32
#pragma mark Copy/Release support
#endif
static void *_Block_copy_internal(const void *arg, const bool wantsOne) {
struct Block_layout *aBlock;
if (!arg) return NULL;
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GC) {
if (wantsOne && ((latching_incr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK) == 2)) {
_Block_setHasRefcount(aBlock, true);
}
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
if (!isGC) {
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
memmove(result, aBlock, aBlock->descriptor->size); result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); result->flags |= BLOCK_NEEDS_FREE | 2; result->isa = _NSConcreteMallocBlock;
_Block_call_copy_helper(result, aBlock);
return result;
}
else {
unsigned long int flags = aBlock->flags;
bool hasCTOR = (flags & BLOCK_HAS_CTOR) != 0;
struct Block_layout *result = _Block_allocator(aBlock->descriptor->size, wantsOne, hasCTOR || _Block_has_layout(aBlock));
if (!result) return (void *)0;
memmove(result, aBlock, aBlock->descriptor->size); flags &= ~(BLOCK_NEEDS_FREE|BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); if (wantsOne)
flags |= BLOCK_IS_GC | 2;
else
flags |= BLOCK_IS_GC;
result->flags = flags;
_Block_call_copy_helper(result, aBlock);
if (hasCTOR) {
result->isa = _NSConcreteFinalizingBlock;
}
else {
result->isa = _NSConcreteAutoBlock;
}
return result;
}
}
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
struct Block_byref **destp = (struct Block_byref **)dest;
struct Block_byref *src = (struct Block_byref *)arg;
if (src->forwarding->flags & BLOCK_IS_GC) {
; }
else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; copy->forwarding = copy; src->forwarding = copy; copy->size = src->size;
if (isWeak) {
copy->isa = &_NSConcreteWeakBlockVariable; }
if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
copy->byref_keep = src->byref_keep;
copy->byref_destroy = src->byref_destroy;
(*src->byref_keep)(copy, src);
}
else {
_Block_memmove(
(void *)©->byref_keep,
(void *)&src->byref_keep,
src->size - sizeof(struct Block_byref_header));
}
}
else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
_Block_assign(src->forwarding, (void **)destp);
}
static void _Block_byref_release(const void *arg) {
struct Block_byref *shared_struct = (struct Block_byref *)arg;
int refcount;
shared_struct = shared_struct->forwarding;
if ((shared_struct->flags & BLOCK_NEEDS_FREE) == 0) {
return; }
refcount = shared_struct->flags & BLOCK_REFCOUNT_MASK;
if (refcount <= 0) {
printf("_Block_byref_release: Block byref data structure at %p underflowed\n", arg);
}
else if (latching_decr_int_should_deallocate(&shared_struct->flags)) {
if (shared_struct->flags & BLOCK_HAS_COPY_DISPOSE) {
(*shared_struct->byref_destroy)(shared_struct);
}
_Block_deallocator((struct Block_layout *)shared_struct);
}
}
#if !TARGET_OS_WIN32
#pragma mark SPI/API
#endif
void *_Block_copy(const void *arg) {
return _Block_copy_internal(arg, true);
}
void _Block_release(const void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock
|| (aBlock->flags & BLOCK_IS_GLOBAL)
|| ((aBlock->flags & (BLOCK_IS_GC|BLOCK_NEEDS_FREE)) == 0)
) return;
if (aBlock->flags & BLOCK_IS_GC) {
if (latching_decr_int_now_zero(&aBlock->flags)) {
_Block_setHasRefcount(aBlock, false);
}
}
else if (aBlock->flags & BLOCK_NEEDS_FREE) {
if (latching_decr_int_should_deallocate(&aBlock->flags)) {
_Block_call_dispose_helper(aBlock);
_Block_destructInstance(aBlock);
_Block_deallocator(aBlock);
}
}
}
bool _Block_tryRetain(const void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
return latching_incr_int_not_deallocating(&aBlock->flags);
}
bool _Block_isDeallocating(const void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
return (aBlock->flags & BLOCK_DEALLOCATING) != 0;
}
static void _Block_destroy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return;
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_IS_GC) {
return; }
_Block_release(aBlock);
}
void *_Block_copy_collectable(const void *aBlock) {
return _Block_copy_internal(aBlock, false);
}
unsigned long int Block_size(void *aBlock) {
return ((struct Block_layout *)aBlock)->descriptor->size;
}
bool _Block_use_stret(void *aBlock) {
struct Block_layout *layout = (struct Block_layout *)aBlock;
int requiredFlags = BLOCK_HAS_SIGNATURE | BLOCK_USE_STRET;
return (layout->flags & requiredFlags) == requiredFlags;
}
bool _Block_has_signature(void *aBlock) {
return _Block_signature(aBlock) ? true : false;
}
const char * _Block_signature(void *aBlock)
{
struct Block_descriptor_3 *desc3 = _Block_descriptor_3(aBlock);
if (!desc3) return NULL;
return desc3->signature;
}
const char * _Block_layout(void *aBlock)
{
struct Block_descriptor_3 *desc3 = _Block_descriptor_3(aBlock);
if (!desc3) return NULL;
return desc3->layout;
}
#if !TARGET_OS_WIN32
#pragma mark Compiler SPI entry points
#endif
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
_Block_assign_weak(object, destAddr);
}
else {
_Block_assign((void *)object, destAddr);
}
}
else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF) {
_Block_byref_assign_copy(destAddr, object, flags);
}
else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
_Block_assign(_Block_copy_internal(object, false), destAddr);
}
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
_Block_retain_object(object);
_Block_assign((void *)object, destAddr);
}
}
void _Block_object_dispose(const void *object, const int flags) {
if (flags & BLOCK_FIELD_IS_BYREF) {
_Block_byref_release(object);
}
else if ((flags & (BLOCK_FIELD_IS_BLOCK|BLOCK_BYREF_CALLER)) == BLOCK_FIELD_IS_BLOCK) {
_Block_destroy(object);
}
else if ((flags & (BLOCK_FIELD_IS_WEAK|BLOCK_FIELD_IS_BLOCK|BLOCK_BYREF_CALLER)) == BLOCK_FIELD_IS_OBJECT) {
_Block_release_object(object);
}
}
#if !TARGET_OS_WIN32
#pragma mark Debugging
#endif
const char *_Block_dump(const void *block) {
struct Block_layout *closure = (struct Block_layout *)block;
static char buffer[512];
char *cp = buffer;
if (closure == NULL) {
sprintf(cp, "NULL passed to _Block_dump\n");
return buffer;
}
cp += sprintf(cp, "^%p (new layout) =\n", closure);
if (closure->isa == NULL) {
cp += sprintf(cp, "isa: NULL\n");
}
else if (closure->isa == _NSConcreteStackBlock) {
cp += sprintf(cp, "isa: stack Block\n");
}
else if (closure->isa == _NSConcreteMallocBlock) {
cp += sprintf(cp, "isa: malloc heap Block\n");
}
else if (closure->isa == _NSConcreteAutoBlock) {
cp += sprintf(cp, "isa: GC heap Block\n");
}
else if (closure->isa == _NSConcreteGlobalBlock) {
cp += sprintf(cp, "isa: global Block\n");
}
else if (closure->isa == _NSConcreteFinalizingBlock) {
cp += sprintf(cp, "isa: finalizing Block\n");
}
else {
cp += sprintf(cp, "isa?: %p\n", closure->isa);
}
cp += sprintf(cp, "flags:");
if (closure->flags & BLOCK_HAS_SIGNATURE) {
cp += sprintf(cp, " HASSIGNATURE");
}
if (closure->flags & BLOCK_USE_STRET) {
cp += sprintf(cp, " STRET");
}
if (closure->flags & BLOCK_NEEDS_FREE) {
cp += sprintf(cp, " FREEME");
}
if (closure->flags & BLOCK_IS_GC) {
cp += sprintf(cp, " ISGC");
}
if (closure->flags & BLOCK_HAS_COPY_DISPOSE) {
cp += sprintf(cp, " HASHELP");
}
if (closure->flags & BLOCK_HAS_CTOR) {
cp += sprintf(cp, " HASCTOR");
}
cp += sprintf(cp, "\nrefcount+deallocating: %u\n", closure->flags & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING));
cp += sprintf(cp, "invoke: %p\n", closure->invoke);
{
struct Block_descriptor_1 *desc1 = _Block_descriptor_1(closure);
struct Block_descriptor_2 *desc2 = _Block_descriptor_2(closure);
struct Block_descriptor_3 *desc3 = _Block_descriptor_3(closure);
if (desc1) {
cp += sprintf(cp, "descriptor: %p\n", desc1);
cp += sprintf(cp, "descriptor->reserved: %lu\n", desc1->reserved);
cp += sprintf(cp, "descriptor->size: %lu\n", desc1->size);
}
if (desc2) {
cp += sprintf(cp, "descriptor->copy helper: %p\n", desc2->copy);
cp += sprintf(cp, "descriptor->dispose helper: %p\n", desc2->dispose);
}
if (desc3) {
cp += sprintf(cp, "descriptor->signature: %p '%s'\n", desc3->signature, desc3->signature);
cp += sprintf(cp, "descriptor->layout: %p '%s'\n", desc3->layout, desc3->layout);
}
}
return buffer;
}
const char *_Block_byref_dump(struct Block_byref *src) {
static char buffer[256];
char *cp = buffer;
cp += sprintf(cp, "byref data block %p contents:\n", src);
cp += sprintf(cp, " forwarding: %p\n", src->forwarding);
cp += sprintf(cp, " flags: 0x%x\n", src->flags);
cp += sprintf(cp, " size: %d\n", src->size);
if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
cp += sprintf(cp, " copy helper: %p\n", src->byref_keep);
cp += sprintf(cp, " dispose helper: %p\n", src->byref_destroy);
}
return buffer;
}