/*
* Copyright (c) 2008, 2009, 2010, 2011
* Jonathan Schleifer <js@webkeks.org>
*
* All rights reserved.
*
* This file is part of ObjFW. It may be distributed under the terms of the
* Q Public License 1.0, which can be found in the file LICENSE.QPL included in
* the packaging of this file.
*
* Alternatively, it may be distributed under the terms of the GNU General
* Public License, either version 2 or 3, which can be found in the file
* LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
* file.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#import "OFObject.h"
#import "OFAutoreleasePool.h"
#import "OFAllocFailedException.h"
#import "OFEnumerationMutationException.h"
#import "OFInitializationFailedException.h"
#import "OFInvalidArgumentException.h"
#import "OFMemoryNotPartOfObjectException.h"
#import "OFNotImplementedException.h"
#import "OFOutOfMemoryException.h"
#import "OFOutOfRangeException.h"
#import "macros.h"
#if defined(OF_OBJFW_RUNTIME)
# import <objfw-rt.h>
#elif defined(OF_OLD_GNU_RUNTIME)
# import <objc/objc-api.h>
# import <objc/sarray.h>
# import <objc/Protocol.h>
#else
# import <objc/runtime.h>
#endif
#ifdef _WIN32
# include <windows.h>
#endif
#import "OFString.h"
#if defined(OF_ATOMIC_OPS)
# import "atomic.h"
#elif defined(OF_THREADS)
# import "threading.h"
#endif
/* A few macros to reduce #ifdefs */
#ifdef OF_OLD_GNU_RUNTIME
# define class_getInstanceSize class_get_instance_size
# define class_getName class_get_class_name
# define class_getSuperclass class_get_super_class
# define sel_registerName sel_get_uid
#endif
struct pre_ivar {
void **memchunks;
size_t memchunks_size;
int32_t retain_count;
#if !defined(OF_ATOMIC_OPS)
of_spinlock_t retain_spinlock;
#endif
};
/* Hopefully no arch needs more than 16 bytes padding */
#ifndef __BIGGEST_ALIGNMENT__
# define __BIGGEST_ALIGNMENT__ 16
#endif
#define PRE_IVAR_ALIGN ((sizeof(struct pre_ivar) + \
(__BIGGEST_ALIGNMENT__ - 1)) & ~(__BIGGEST_ALIGNMENT__ - 1))
#define PRE_IVAR ((struct pre_ivar*)(void*)((char*)self - PRE_IVAR_ALIGN))
static struct {
Class isa;
} alloc_failed_exception;
static Class autoreleasepool = Nil;
static SEL cxx_construct = NULL;
static SEL cxx_destruct = NULL;
size_t of_pagesize;
#ifdef NEED_OBJC_SYNC_INIT
extern BOOL objc_sync_init();
#endif
#ifdef NEED_OBJC_PROPERTIES_INIT
extern BOOL objc_properties_init();
#endif
static void
enumeration_mutation_handler(id obj)
{
@throw [OFEnumerationMutationException newWithClass: [obj class]
object: obj];
}
#ifndef HAVE_OBJC_ENUMERATIONMUTATION
void
objc_enumerationMutation(id obj)
{
enumeration_mutation_handler(obj);
}
#endif
@implementation OFObject
+ (void)load
{
#ifdef NEED_OBJC_SYNC_INIT
if (!objc_sync_init()) {
fputs("Runtime error: objc_sync_init() failed!\n", stderr);
abort();
}
#endif
#ifdef NEED_OBJC_PROPERTIES_INIT
if (!objc_properties_init()) {
fputs("Runtime error: objc_properties_init() failed!\n",
stderr);
abort();
}
#endif
#if defined(OF_APPLE_RUNTIME) || defined(OF_GNU_RUNTIME)
objc_setEnumerationMutationHandler(enumeration_mutation_handler);
#endif
cxx_construct = sel_registerName(".cxx_construct");
cxx_destruct = sel_registerName(".cxx_destruct");
if (cxx_construct == NULL || cxx_destruct == NULL) {
fputs("Runtime error: Failed to register selector "
".cxx_construct and/or .cxx_destruct!\n", stderr);
abort();
}
#if defined(_WIN32)
SYSTEM_INFO si;
GetSystemInfo(&si);
of_pagesize = si.dwPageSize;
#elif defined(_PSP)
of_pagesize = 4096;
#else
if ((of_pagesize = sysconf(_SC_PAGESIZE)) < 1)
of_pagesize = 4096;
#endif
}
+ (void)initialize
{
}
+ alloc
{
OFObject *instance;
size_t isize = class_getInstanceSize(self);
Class class;
void (*last)(id, SEL) = NULL;
if ((instance = malloc(isize + PRE_IVAR_ALIGN)) == NULL) {
alloc_failed_exception.isa = [OFAllocFailedException class];
@throw (OFAllocFailedException*)&alloc_failed_exception;
}
((struct pre_ivar*)instance)->memchunks = NULL;
((struct pre_ivar*)instance)->memchunks_size = 0;
((struct pre_ivar*)instance)->retain_count = 1;
#if !defined(OF_ATOMIC_OPS)
if (!of_spinlock_new(&((struct pre_ivar*)instance)->retain_spinlock)) {
free(instance);
@throw [OFInitializationFailedException newWithClass: self];
}
#endif
instance = (OFObject*)((char*)instance + PRE_IVAR_ALIGN);
memset(instance, 0, isize);
instance->isa = self;
for (class = self; class != Nil; class = class_getSuperclass(class)) {
void (*construct)(id, SEL);
if ([class instancesRespondToSelector: cxx_construct]) {
if ((construct = (void(*)(id, SEL))[class
instanceMethodForSelector: cxx_construct]) != last)
construct(instance, cxx_construct);
last = construct;
} else
break;
}
return instance;
}
+ (Class)class
{
return self;
}
+ (OFString*)className
{
return [OFString stringWithCString: class_getName(self)];
}
+ (BOOL)isSubclassOfClass: (Class)class
{
Class iter;
for (iter = self; iter != Nil; iter = class_getSuperclass(iter))
if (iter == class)
return YES;
return NO;
}
+ (Class)superclass
{
return class_getSuperclass(self);
}
+ (BOOL)instancesRespondToSelector: (SEL)selector
{
#ifdef OF_OLD_GNU_RUNTIME
return class_get_instance_method(self, selector) != METHOD_NULL;
#else
return class_respondsToSelector(self, selector);
#endif
}
+ (BOOL)conformsToProtocol: (Protocol*)protocol
{
#ifdef OF_OLD_GNU_RUNTIME
Class c;
struct objc_protocol_list *pl;
size_t i;
for (c = self; c != Nil; c = class_get_super_class(c))
for (pl = c->protocols; pl != NULL; pl = pl->next)
for (i = 0; i < pl->count; i++)
if ([pl->list[i] conformsTo: protocol])
return YES;
return NO;
#else
Class c;
for (c = self; c != Nil; c = class_getSuperclass(c))
if (class_conformsToProtocol(c, protocol))
return YES;
return NO;
#endif
}
+ (IMP)instanceMethodForSelector: (SEL)selector
{
#if defined(OF_OBJFW_RUNTIME)
return objc_get_instance_method(self, selector);
#elif defined(OF_OLD_GNU_RUNTIME)
return method_get_imp(class_get_instance_method(self, selector));
#else
return class_getMethodImplementation(self, selector);
#endif
}
+ (const char*)typeEncodingForInstanceSelector: (SEL)selector
{
#if defined(OF_OBJFW_RUNTIME)
const char *ret;
if ((ret = objc_get_type_encoding(self, selector)) == NULL)
@throw [OFNotImplementedException newWithClass: self
selector: selector];
return ret;
#elif defined(OF_OLD_GNU_RUNTIME)
Method_t m;
if ((m = class_get_instance_method(self, selector)) == NULL ||
m->method_types == NULL)
@throw [OFNotImplementedException newWithClass: self
selector: selector];
return m->method_types;
#else
Method m;
const char *ret;
if ((m = class_getInstanceMethod(self, selector)) == NULL ||
(ret = method_getTypeEncoding(m)) == NULL)
@throw [OFNotImplementedException newWithClass: self
selector: selector];
return ret;
#endif
}
+ (OFString*)description
{
return [self className];
}
+ (IMP)setImplementation: (IMP)newimp
forClassMethod: (SEL)selector
{
#if defined(OF_OBJFW_RUNTIME)
if (newimp == (IMP)0 || !class_respondsToSelector(self->isa, selector))
@throw [OFInvalidArgumentException newWithClass: self
selector: _cmd];
return objc_replace_class_method(self, selector, newimp);
#elif defined(OF_OLD_GNU_RUNTIME)
Method_t method;
IMP oldimp;
/* The class method is the instance method of the meta class */
if ((method = class_get_instance_method(self->class_pointer,
selector)) == NULL)
@throw [OFInvalidArgumentException newWithClass: self
selector: _cmd];
if ((oldimp = method_get_imp(method)) == (IMP)0 || newimp == (IMP)0)
@throw [OFInvalidArgumentException newWithClass: self
selector: _cmd];
method->method_imp = newimp;
/* Update the dtable if necessary */
if (sarray_get_safe(((Class)self->class_pointer)->dtable,
(sidx)method->method_name->sel_id))
sarray_at_put_safe(((Class)self->class_pointer)->dtable,
(sidx)method->method_name->sel_id, method->method_imp);
return oldimp;
#else
Method method;
if (newimp == (IMP)0 ||
(method = class_getClassMethod(self, selector)) == NULL)
@throw [OFInvalidArgumentException newWithClass: self
selector: _cmd];
/*
* Cast needed because it's isa in the Apple runtime, but class_pointer
* in the GNU runtime.
*/
return class_replaceMethod(((OFObject*)self)->isa, selector, newimp,
method_getTypeEncoding(method));
#endif
}
+ (IMP)replaceClassMethod: (SEL)selector
withMethodFromClass: (Class)class
{
IMP newimp;
if (![class isSubclassOfClass: self])
@throw [OFInvalidArgumentException newWithClass: self
selector: _cmd];
newimp = [class methodForSelector: selector];
return [self setImplementation: newimp
forClassMethod: selector];
}
+ (IMP)setImplementation: (IMP)newimp
forInstanceMethod: (SEL)selector
{
#if defined(OF_OBJFW_RUNTIME)
if (newimp == (IMP)0 || !class_respondsToSelector(self, selector))
@throw [OFInvalidArgumentException newWithClass: self
selector: _cmd];
return objc_replace_instance_method(self, selector, newimp);
#elif defined(OF_OLD_GNU_RUNTIME)
Method_t method = class_get_instance_method(self, selector);
IMP oldimp;
if (method == NULL)
@throw [OFInvalidArgumentException newWithClass: self
selector: _cmd];
if ((oldimp = method_get_imp(method)) == (IMP)0 || newimp == (IMP)0)
@throw [OFInvalidArgumentException newWithClass: self
selector: _cmd];
method->method_imp = newimp;
/* Update the dtable if necessary */
if (sarray_get_safe(((Class)self)->dtable,
(sidx)method->method_name->sel_id))
sarray_at_put_safe(((Class)self)->dtable,
(sidx)method->method_name->sel_id, method->method_imp);
return oldimp;
#else
Method method;
if (newimp == (IMP)0 ||
(method = class_getInstanceMethod(self, selector)) == NULL)
@throw [OFInvalidArgumentException newWithClass: self
selector: _cmd];
return class_replaceMethod(self, selector, newimp,
method_getTypeEncoding(method));
#endif
}
+ (IMP)replaceInstanceMethod: (SEL)selector
withMethodFromClass: (Class)class
{
IMP newimp;
if (![class isSubclassOfClass: self])
@throw [OFInvalidArgumentException newWithClass: self
selector: _cmd];
newimp = [class instanceMethodForSelector: selector];
return [self setImplementation: newimp
forInstanceMethod: selector];
}
- init
{
return self;
}
- (Class)class
{
return isa;
}
- (OFString*)className
{
return [OFString stringWithCString: class_getName(isa)];
}
- (BOOL)isKindOfClass: (Class)class
{
Class iter;
for (iter = isa; iter != Nil; iter = class_getSuperclass(iter))
if (iter == class)
return YES;
return NO;
}
- (BOOL)respondsToSelector: (SEL)selector
{
#ifdef OF_OLD_GNU_RUNTIME
if (object_is_instance(self))
return class_get_instance_method(isa, selector) != METHOD_NULL;
else
return class_get_class_method(isa, selector) != METHOD_NULL;
#else
return class_respondsToSelector(isa, selector);
#endif
}
- (BOOL)conformsToProtocol: (Protocol*)protocol
{
return [isa conformsToProtocol: protocol];
}
- (IMP)methodForSelector: (SEL)selector
{
#if defined(OF_OBJFW_RUNTIME) || defined(OF_OLD_GNU_RUNTIME)
return objc_msg_lookup(self, selector);
#else
return class_getMethodImplementation(isa, selector);
#endif
}
- (id)performSelector: (SEL)selector
{
id (*imp)(id, SEL) = (id(*)(id, SEL))[self methodForSelector: selector];
return imp(self, selector);
}
- (id)performSelector: (SEL)selector
withObject: (id)obj
{
id (*imp)(id, SEL, id) =
(id(*)(id, SEL, id))[self methodForSelector: selector];
return imp(self, selector, obj);
}
- (id)performSelector: (SEL)selector
withObject: (id)obj1
withObject: (id)obj2
{
id (*imp)(id, SEL, id, id) =
(id(*)(id, SEL, id, id))[self methodForSelector: selector];
return imp(self, selector, obj1, obj2);
}
- (const char*)typeEncodingForSelector: (SEL)selector
{
#if defined(OF_OBJFW_RUNTIME)
const char *ret;
if ((ret = objc_get_type_encoding(isa, selector)) == NULL)
@throw [OFNotImplementedException newWithClass: isa
selector: selector];
return ret;
#elif defined(OF_OLD_GNU_RUNTIME)
Method_t m;
if ((m = class_get_instance_method(isa, selector)) == NULL ||
m->method_types == NULL)
@throw [OFNotImplementedException newWithClass: isa
selector: selector];
return m->method_types;
#else
Method m;
const char *ret;
if ((m = class_getInstanceMethod(isa, selector)) == NULL ||
(ret = method_getTypeEncoding(m)) == NULL)
@throw [OFNotImplementedException newWithClass: isa
selector: selector];
return ret;
#endif
}
- (BOOL)isEqual: (id)obj
{
/* Classes containing data should reimplement this! */
return (self == obj ? YES : NO);
}
- (uint32_t)hash
{
/* Classes containing data should reimplement this! */
return (uint32_t)(uintptr_t)self;
}
- (OFString*)description
{
/* Classes containing data should reimplement this! */
return [OFString stringWithFormat: @"<%@: %p>", [self className], self];
}
- (void)addMemoryToPool: (void*)ptr
{
void **memchunks;
size_t memchunks_size;
memchunks_size = PRE_IVAR->memchunks_size + 1;
if (SIZE_MAX - PRE_IVAR->memchunks_size < 1 ||
memchunks_size > SIZE_MAX / sizeof(void*))
@throw [OFOutOfRangeException newWithClass: isa];
if ((memchunks = realloc(PRE_IVAR->memchunks,
memchunks_size * sizeof(void*))) == NULL)
@throw [OFOutOfMemoryException newWithClass: isa
requestedSize: memchunks_size];
PRE_IVAR->memchunks = memchunks;
PRE_IVAR->memchunks[PRE_IVAR->memchunks_size] = ptr;
PRE_IVAR->memchunks_size = memchunks_size;
}
- (void*)allocMemoryWithSize: (size_t)size
{
void *ptr, **memchunks;
size_t memchunks_size;
if (size == 0)
return NULL;
memchunks_size = PRE_IVAR->memchunks_size + 1;
if (SIZE_MAX - PRE_IVAR->memchunks_size == 0 ||
memchunks_size > SIZE_MAX / sizeof(void*))
@throw [OFOutOfRangeException newWithClass: isa];
if ((ptr = malloc(size)) == NULL)
@throw [OFOutOfMemoryException newWithClass: isa
requestedSize: size];
if ((memchunks = realloc(PRE_IVAR->memchunks,
memchunks_size * sizeof(void*))) == NULL) {
free(ptr);
@throw [OFOutOfMemoryException newWithClass: isa
requestedSize: memchunks_size];
}
PRE_IVAR->memchunks = memchunks;
PRE_IVAR->memchunks[PRE_IVAR->memchunks_size] = ptr;
PRE_IVAR->memchunks_size = memchunks_size;
return ptr;
}
- (void*)allocMemoryForNItems: (size_t)nitems
withSize: (size_t)size
{
if (nitems == 0 || size == 0)
return NULL;
if (nitems > SIZE_MAX / size)
@throw [OFOutOfRangeException newWithClass: isa];
return [self allocMemoryWithSize: nitems * size];
}
- (void*)resizeMemory: (void*)ptr
toSize: (size_t)size
{
void **iter;
if (ptr == NULL)
return [self allocMemoryWithSize: size];
if (size == 0) {
[self freeMemory: ptr];
return NULL;
}
iter = PRE_IVAR->memchunks + PRE_IVAR->memchunks_size;
while (iter-- > PRE_IVAR->memchunks) {
if (OF_UNLIKELY(*iter == ptr)) {
if (OF_UNLIKELY((ptr = realloc(ptr, size)) == NULL))
@throw [OFOutOfMemoryException
newWithClass: isa
requestedSize: size];
*iter = ptr;
return ptr;
}
}
@throw [OFMemoryNotPartOfObjectException newWithClass: isa
pointer: ptr];
}
- (void*)resizeMemory: (void*)ptr
toNItems: (size_t)nitems
withSize: (size_t)size
{
if (ptr == NULL)
return [self allocMemoryForNItems: nitems
withSize: size];
if (nitems == 0 || size == 0) {
[self freeMemory: ptr];
return NULL;
}
if (nitems > SIZE_MAX / size)
@throw [OFOutOfRangeException newWithClass: isa];
return [self resizeMemory: ptr
toSize: nitems * size];
}
- (void)freeMemory: (void*)ptr
{
void **iter, *last, **memchunks;
size_t i, memchunks_size;
if (ptr == NULL)
return;
iter = PRE_IVAR->memchunks + PRE_IVAR->memchunks_size;
i = PRE_IVAR->memchunks_size;
while (iter-- > PRE_IVAR->memchunks) {
i--;
if (OF_UNLIKELY(*iter == ptr)) {
memchunks_size = PRE_IVAR->memchunks_size - 1;
last = PRE_IVAR->memchunks[memchunks_size];
assert(PRE_IVAR->memchunks_size != 0 &&
memchunks_size <= SIZE_MAX / sizeof(void*));
if (OF_UNLIKELY(memchunks_size == 0)) {
free(ptr);
free(PRE_IVAR->memchunks);
PRE_IVAR->memchunks = NULL;
PRE_IVAR->memchunks_size = 0;
return;
}
free(ptr);
PRE_IVAR->memchunks[i] = last;
PRE_IVAR->memchunks_size = memchunks_size;
if (OF_UNLIKELY((memchunks = realloc(
PRE_IVAR->memchunks, memchunks_size *
sizeof(void*))) == NULL))
return;
PRE_IVAR->memchunks = memchunks;
return;
}
}
@throw [OFMemoryNotPartOfObjectException newWithClass: isa
pointer: ptr];
}
- retain
{
#if defined(OF_ATOMIC_OPS)
of_atomic_inc_32(&PRE_IVAR->retain_count);
#else
assert(of_spinlock_lock(&PRE_IVAR->retain_spinlock));
PRE_IVAR->retain_count++;
assert(of_spinlock_unlock(&PRE_IVAR->retain_spinlock));
#endif
return self;
}
- (unsigned int)retainCount
{
assert(PRE_IVAR->retain_count >= 0);
return PRE_IVAR->retain_count;
}
- (void)release
{
#if defined(OF_ATOMIC_OPS)
if (of_atomic_dec_32(&PRE_IVAR->retain_count) <= 0)
[self dealloc];
#else
size_t c;
assert(of_spinlock_lock(&PRE_IVAR->retain_spinlock));
c = --PRE_IVAR->retain_count;
assert(of_spinlock_unlock(&PRE_IVAR->retain_spinlock));
if (!c)
[self dealloc];
#endif
}
- autorelease
{
/*
* Cache OFAutoreleasePool since class lookups are expensive with the
* GNU runtime.
*/
if (autoreleasepool == Nil)
autoreleasepool = [OFAutoreleasePool class];
[autoreleasepool addObject: self];
return self;
}
- (void)dealloc
{
Class class;
void (*last)(id, SEL) = NULL;
void **iter;
for (class = isa; class != Nil; class = class_getSuperclass(class)) {
void (*destruct)(id, SEL);
if ([class instancesRespondToSelector: cxx_destruct]) {
if ((destruct = (void(*)(id, SEL))[class
instanceMethodForSelector: cxx_destruct]) != last)
destruct(self, cxx_destruct);
last = destruct;
} else
break;
}
iter = PRE_IVAR->memchunks + PRE_IVAR->memchunks_size;
while (iter-- > PRE_IVAR->memchunks)
free(*iter);
if (PRE_IVAR->memchunks != NULL)
free(PRE_IVAR->memchunks);
free((char*)self - PRE_IVAR_ALIGN);
}
/* Required to use properties with the Apple runtime */
- copyWithZone: (void*)zone
{
if (zone != NULL)
@throw [OFNotImplementedException newWithClass: isa
selector: _cmd];
return [(id)self copy];
}
- mutableCopyWithZone: (void*)zone
{
if (zone != NULL)
@throw [OFNotImplementedException newWithClass: isa
selector: _cmd];
return [(id)self mutableCopy];
}
/*
* Those are needed as the root class is the superclass of the root class's
* metaclass and thus instance methods can be sent to class objects as well.
*/
+ (void)addMemoryToPool: (void*)ptr
{
@throw [OFNotImplementedException newWithClass: self
selector: _cmd];
}
+ (void*)allocMemoryWithSize: (size_t)size
{
@throw [OFNotImplementedException newWithClass: self
selector: _cmd];
}
+ (void*)allocMemoryForNItems: (size_t)nitems
withSize: (size_t)size
{
@throw [OFNotImplementedException newWithClass: self
selector: _cmd];
}
+ (void*)resizeMemory: (void*)ptr
toSize: (size_t)size
{
@throw [OFNotImplementedException newWithClass: self
selector: _cmd];
}
+ (void*)resizeMemory: (void*)ptr
toNItems: (size_t)nitems
withSize: (size_t)size
{
@throw [OFNotImplementedException newWithClass: self
selector: _cmd];
}
+ (void)freeMemory: (void*)ptr
{
@throw [OFNotImplementedException newWithClass: self
selector: _cmd];
}
+ retain
{
return self;
}
+ autorelease
{
return self;
}
+ (unsigned int)retainCount
{
return OF_RETAIN_COUNT_MAX;
}
+ (void)release
{
}
+ (void)dealloc
{
@throw [OFNotImplementedException newWithClass: self
selector: _cmd];
}
+ copyWithZone: (void*)zone
{
@throw [OFNotImplementedException newWithClass: self
selector: _cmd];
}
+ mutableCopyWithZone: (void*)zone
{
@throw [OFNotImplementedException newWithClass: self
selector: _cmd];
}
@end