ObjFW  Artifact [a0cbb15bb4]

Artifact a0cbb15bb48009e4e5bd3f386d7e9efb37e3826414dee3f4d84fa5aceb06679e:

  • File src/OFObject.m — part of check-in [57fb5578cc] at 2009-04-19 17:37:52 on branch trunk — Remove forwarding methods. See long commit message for details.

    There are two resons for removing it:

    First, OFPlugin does not need forwarding anymore. Second is that
    forwarding is broken in both, the GNU and the Apple runtime.

    In GNU libobjc, objc_msg_sendv is implemented using __builtin_apply,
    which is broken on many platforms, including x86_64. If forwarding is
    used, the application will just crash. To work around that, I'd need to
    parse the type encoding and use libffi to call the method instead of
    using objc_msg_sendv.

    Now the Apple runtime has a similar problem: There is no objc_msgSendv
    for PPC64 and x86_64 as it's only in ObjC1 and on ARM (iPhone), it's
    broken (most likely because the iPhone uses only ObjC2 - I was confused
    that objc_msgSendv was even in the libobjc there). So I'd need to write
    an ASM implementation for these 3.

    Writing those 3 ASM implementations (or 5, so we don't depend on ObjC1
    stuff on PPC32 and x86 as well) wouldn't be a problem, but there is a
    problem the GNU libobjc and the Apple runtime got in common, which
    originates from the early ObjC implementations:

    forward:: and performv:: were only designed to return scalar types. But
    today, it's possible to return floats, structs and unions as well. What
    Apple and GNU use here is a very hacky workaround and it's just luck
    that it works. forward:: and performv:: both return an id (Apple) or
    void* (GNU). forward:: is called by the runtime if you called a method
    that is not implemented. The compiler does not know at compile time
    that it is not implemented, therefore expects a float as a return. On
    x86, floats are returned in sp0. The runtime now notices that the
    called method is not implemented and calls forward::. Forward then
    calls performv:: to call the right method. The method returns a float
    and stores it in sp0. Remember that both, forward:: and performv::
    return an id / void*. performv:: returns now and after that, forward::
    returns. The return of those was always put into eax, as that's how
    scalar values are returned on x86. The original caller of the method
    does not expect any return value in eax, but in sp0. This works, as
    no code touched sp0. However, you can not rely on sp0 not being
    touched. It's just luck that the compiler generates code that does not
    touch sp0.

    While this works for forwarding due to the ABI on x86 (and the ABIs on
    many other platforms allow this hack as well), this fails if you call
    performv:: directly on a method returning a float. In this case, the
    compiler does not expect a return value in sp0, but in eax, as
    performv:: is expected to return id / void*. Therefore the bogus value
    in eax will be casted to float and the result will be useless.

    This is why I decided to remove forwarding and performv:: from libobjfw
    for now. If I encounter a situation where I need forwarding, I'm going
    to implement it in a sane way and NOT the objc way. The forwarding
    methods this commit removes did it the objc way, which is IMO just
    wrong. (That way was ok back then when you only had scalar return
    types, but today you're not limited to scalar return types anymore.) (user: js, size: 8307) [annotate] [blame] [check-ins using]


/*
 * Copyright (c) 2008 - 2009
 *   Jonathan Schleifer <js@webkeks.org>
 *
 * All rights reserved.
 *
 * This file is part of libobjfw. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE included in
 * the packaging of this file.
 */

#import "config.h"

#include <stdlib.h>
#include <string.h>
#include <limits.h>

#import "OFObject.h"
#import "OFAutoreleasePool.h"
#import "OFExceptions.h"
#import "OFMacros.h"

#import <objc/objc-api.h>
#ifndef __objc_INCLUDE_GNU
#import <objc/runtime.h>
#endif

struct pre_ivar {
	void   **memchunks;
	size_t memchunks_size;
	size_t retain_count;
};

/* Hopefully no arch needs more than 16 bytes padding */
#define PRE_IVAR_ALIGN ((sizeof(struct pre_ivar) + 15) & ~15)
#define PRE_IVAR ((struct pre_ivar*)((char*)self - PRE_IVAR_ALIGN))

@implementation OFObject
#ifndef __objc_INCLUDE_GNU
+ load
{
	return self;
}
#endif

+ initialize
{
	return self;
}

+ alloc
{
	OFObject *instance;
#ifdef __objc_INCLUDE_GNU
	size_t isize = class_get_instance_size(self);
#else
	size_t isize = class_getInstanceSize(self);
#endif

	if ((instance = malloc(isize + PRE_IVAR_ALIGN)) == NULL)
		return nil;

	((struct pre_ivar*)instance)->memchunks = NULL;
	((struct pre_ivar*)instance)->memchunks_size = 0;
	((struct pre_ivar*)instance)->retain_count = 1;

	instance = (OFObject*)((char*)instance + PRE_IVAR_ALIGN);
	memset(instance, 0, isize);
	instance->isa = self;

	return instance;
}

+ new
{
	return [[self alloc] init];
}

+ (Class)class
{
	return self;
}

+ (const char*)name
{
#ifdef __objc_INCLUDE_GNU
	return class_get_class_name(self);
#else
	return class_getName(self);
#endif
}

+ (IMP)replaceMethod: (SEL)selector
 withMethodFromClass: (Class)class;
{
#ifdef __objc_INCLUDE_GNU
	Method_t method = class_get_instance_method(self, selector);
	IMP oldimp, newimp;

	if (method == NULL)
		@throw [OFInvalidArgumentException newWithClass: self
						    andSelector: _cmd];

	oldimp = method_get_imp(method);
	newimp = method_get_imp(class_get_instance_method(class, selector));

	if (oldimp == (IMP)0 || newimp == (IMP)0)
		@throw [OFInvalidArgumentException newWithClass: self
						    andSelector: _cmd];

	method->method_imp = newimp;
	return oldimp;
#else
	Method m;
	IMP imp;

	if ((m = class_getInstanceMethod(self, selector)) == NULL ||
	    (imp = method_getImplementation(m)) == NULL)
		@throw [OFInvalidArgumentException newWithClass: self
						    andSelector: _cmd];

	return method_setImplementation(m, imp);
#endif
}

- init
{
	return self;
}

- (Class)class
{
	return isa;
}

- (const char*)name
{
#ifdef __objc_INCLUDE_GNU
	return object_get_class_name(self);
#else
	return class_getName(isa);
#endif
}

- (BOOL)isKindOf: (Class)class
{
	Class iter;

#ifdef __objc_INCLUDE_GNU
	for (iter = isa; iter != Nil; iter = class_get_super_class(iter))
#else
	for (iter = isa; iter != Nil; iter = class_getSuperclass(iter))
#endif
		if (iter == class)
			return YES;

	return NO;
}

- (BOOL)respondsTo: (SEL)selector
{
#ifdef __objc_INCLUDE_GNU
	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
}

- (IMP)methodFor: (SEL)selector
{
#ifdef __objc_INCLUDE_GNU
	if (object_is_instance(self))
		return method_get_imp(class_get_instance_method(isa, selector));
	else
		return method_get_imp(class_get_class_method(isa, selector));
#else
	return class_getMethodImplementation(isa, selector);
#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)(intptr_t)self;
}

- addToMemoryPool: (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: [self class]];

	if ((memchunks = realloc(PRE_IVAR->memchunks,
	    memchunks_size * sizeof(void*))) == NULL)
		@throw [OFNoMemException newWithClass: [self class]
					      andSize: memchunks_size];

	PRE_IVAR->memchunks = memchunks;
	PRE_IVAR->memchunks[PRE_IVAR->memchunks_size] = ptr;
	PRE_IVAR->memchunks_size = memchunks_size;

	return self;
}

- (void*)getMemWithSize: (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: [self class]];

	if ((ptr = malloc(size)) == NULL)
		@throw [OFNoMemException newWithClass: [self class]
					      andSize: size];

	if ((memchunks = realloc(PRE_IVAR->memchunks,
	    memchunks_size * sizeof(void*))) == NULL) {
		free(ptr);
		@throw [OFNoMemException newWithClass: [self class]
					      andSize: memchunks_size];
	}

	PRE_IVAR->memchunks = memchunks;
	PRE_IVAR->memchunks[PRE_IVAR->memchunks_size] = ptr;
	PRE_IVAR->memchunks_size = memchunks_size;

	return ptr;
}

- (void*)getMemForNItems: (size_t)nitems
		  ofSize: (size_t)size
{
	if (nitems == 0 || size == 0)
		return NULL;

	if (nitems > SIZE_MAX / size)
		@throw [OFOutOfRangeException newWithClass: [self class]];

	return [self getMemWithSize: nitems * size];
}

- (void*)resizeMem: (void*)ptr
	    toSize: (size_t)size
{
	void **iter;

	if (ptr == NULL)
		return [self getMemWithSize: size];

	if (size == 0) {
		[self freeMem: 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 [OFNoMemException
				    newWithClass: [self class]
					 andSize: size];

			*iter = ptr;
			return ptr;
		}
	}

	@throw [OFMemNotPartOfObjException newWithClass: [self class]
					     andPointer: ptr];
	return NULL;	/* never reached, but makes gcc happy */
}

- (void*)resizeMem: (void*)ptr
	  toNItems: (size_t)nitems
	    ofSize: (size_t)size
{
	size_t memsize;

	if (ptr == NULL)
		return [self getMemForNItems: nitems
				      ofSize: size];

	if (nitems == 0 || size == 0) {
		[self freeMem: ptr];
		return NULL;
	}

	if (nitems > SIZE_MAX / size)
		@throw [OFOutOfRangeException newWithClass: [self class]];

	memsize = nitems * size;
	return [self resizeMem: ptr
			toSize: memsize];
}

- freeMem: (void*)ptr;
{
	void **iter, *last, **memchunks;
	size_t i, memchunks_size;

	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];

			if (OF_UNLIKELY(PRE_IVAR->memchunks_size == 0 ||
			    memchunks_size > SIZE_MAX / sizeof(void*)))
				@throw [OFOutOfRangeException
				    newWithClass: [self class]];

			if (OF_UNLIKELY(memchunks_size == 0)) {
				free(ptr);
				free(PRE_IVAR->memchunks);

				PRE_IVAR->memchunks = NULL;
				PRE_IVAR->memchunks_size = 0;

				return self;
			}

			if (OF_UNLIKELY((memchunks = realloc(
			    PRE_IVAR->memchunks, memchunks_size *
			    sizeof(void*))) == NULL))
				@throw [OFNoMemException
				    newWithClass: [self class]
					 andSize: memchunks_size];

			free(ptr);
			PRE_IVAR->memchunks = memchunks;
			PRE_IVAR->memchunks[i] = last;
			PRE_IVAR->memchunks_size = memchunks_size;

			return self;
		}
	}

	@throw [OFMemNotPartOfObjException newWithClass: [self class]
					     andPointer: ptr];
	return self;	/* never reached, but makes gcc happy */
}

- retain
{
	PRE_IVAR->retain_count++;

	return self;
}

- autorelease
{
	[OFAutoreleasePool addToPool: self];

	return self;
}

- (size_t)retainCount
{
	return PRE_IVAR->retain_count;
}

- (void)release
{
	if (!--PRE_IVAR->retain_count)
		[self free];
}

- free
{
	void **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);
	return nil;
}
@end