/*
* Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
*
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3.0 only,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* version 3.0 for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3.0 along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#import "ObjFW.h"
#import "ObjFWTest.h"
#define FORMAT @"%@ %@ %@ %@ %@ %@ %@ %@ %@ %g %g %g %g %g %g %g %g %g"
#define ARGS @"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", \
1.5, 2.25, 3.125, 4.0625, 5.03125, 6.5, 7.25, 8.0, 9.0
#define RESULT @"a b c d e f g h i 1.5 2.25 3.125 4.0625 5.03125 6.5 7.25 8 9"
@interface ForwardingTests: OTTestCase
@end
static size_t forwardingsCount = 0;
static bool success = false;
static id target = nil;
struct StretTest {
char buffer[1024];
};
@interface ForwardingTestObject: OFObject
@end
@interface ForwardingTestObject (NonExistentMethods)
+ (void)test;
- (void)test;
- (uint32_t)forwardingTargetTest: (intptr_t)a0
: (intptr_t)a1
: (double)a2
: (double)a3;
- (OFString *)forwardingTargetVarArgTest: (OFConstantString *)format, ...;
- (long double)forwardingTargetFPRetTest;
- (struct StretTest)forwardingTargetStRetTest;
- (void)forwardingTargetNilTest;
- (void)forwardingTargetSelfTest;
- (struct StretTest)forwardingTargetNilStRetTest;
- (struct StretTest)forwardingTargetSelfStRetTest;
@end
@interface ForwardingTarget: OFObject
@end
static void
test(id self, SEL _cmd)
{
success = true;
}
@implementation ForwardingTests
- (void)setUp
{
[super setUp];
forwardingsCount = 0;
success = false;
target = nil;
}
- (void)testForwardingMessageAndAddingClassMethod
{
[ForwardingTestObject test];
OTAssertTrue(success);
OTAssertEqual(forwardingsCount, 1);
[ForwardingTestObject test];
OTAssertEqual(forwardingsCount, 1);
}
- (void)forwardingMessageAndAddingInstanceMethod
{
ForwardingTestObject *testObject =
[[[ForwardingTestObject alloc] init] autorelease];
[testObject test];
OTAssertTrue(success);
OTAssertEqual(forwardingsCount, 1);
[testObject test];
OTAssertEqual(forwardingsCount, 1);
}
#ifdef OF_HAVE_FORWARDING_TARGET_FOR_SELECTOR
- (void)testForwardingTargetForSelector
{
ForwardingTestObject *testObject =
[[[ForwardingTestObject alloc] init] autorelease];
target = [[[ForwardingTarget alloc] init] autorelease];
OTAssertEqual(
[testObject forwardingTargetTest: 0xDEADBEEF
: -1
: 1.25
: 2.75], 0x12345678);
}
- (void)testForwardingTargetForSelectorWithVariableArguments
{
ForwardingTestObject *testObject =
[[[ForwardingTestObject alloc] init] autorelease];
target = [[[ForwardingTarget alloc] init] autorelease];
OTAssertEqualObjects(
([testObject forwardingTargetVarArgTest: FORMAT, ARGS]), RESULT);
}
/*
* Don't try fpret on Win64 if we don't have stret forwarding, as long double
* is handled as a struct there.
*/
# if !defined(OF_WINDOWS) || !defined(OF_AMD64) || \
defined(OF_HAVE_FORWARDING_TARGET_FOR_SELECTOR_STRET)
- (void)testForwardingTargetForSelectorFPRet
{
ForwardingTestObject *testObject =
[[[ForwardingTestObject alloc] init] autorelease];
target = [[[ForwardingTarget alloc] init] autorelease];
OTAssertEqual([testObject forwardingTargetFPRetTest],
12345678.00006103515625);
}
# endif
# ifdef OF_HAVE_FORWARDING_TARGET_FOR_SELECTOR_STRET
- (void)testForwardingTargetForSelectorStRet
{
ForwardingTestObject *testObject =
[[[ForwardingTestObject alloc] init] autorelease];
target = [[[ForwardingTarget alloc] init] autorelease];
OTAssertEqual(memcmp([testObject forwardingTargetStRetTest].buffer,
"abcdefghijklmnopqrstuvwxyz", 27), 0);
}
# endif
- (void)testForwardingTargetForSelectorReturningNilThrows
{
ForwardingTestObject *testObject =
[[[ForwardingTestObject alloc] init] autorelease];
target = [[[ForwardingTarget alloc] init] autorelease];
OTAssertThrowsSpecific([testObject forwardingTargetNilTest],
OFNotImplementedException);
}
- (void)testForwardingTargetForSelectorReturningSelfThrows
{
ForwardingTestObject *testObject =
[[[ForwardingTestObject alloc] init] autorelease];
target = [[[ForwardingTarget alloc] init] autorelease];
OTAssertThrowsSpecific([testObject forwardingTargetSelfTest],
OFNotImplementedException);
}
# ifdef OF_HAVE_FORWARDING_TARGET_FOR_SELECTOR_STRET
- (void)testForwardingTargetForSelectorStRetReturningNilThrows
{
ForwardingTestObject *testObject =
[[[ForwardingTestObject alloc] init] autorelease];
target = [[[ForwardingTarget alloc] init] autorelease];
OTAssertThrowsSpecific([testObject forwardingTargetNilStRetTest],
OFNotImplementedException);
}
- (void)testForwardingTargetForSelectorStRetReturningSelfThrows
{
ForwardingTestObject *testObject =
[[[ForwardingTestObject alloc] init] autorelease];
target = [[[ForwardingTarget alloc] init] autorelease];
OTAssertThrowsSpecific([testObject forwardingTargetSelfStRetTest],
OFNotImplementedException);
}
# endif
#endif
@end
@implementation ForwardingTestObject
+ (bool)resolveClassMethod: (SEL)selector
{
forwardingsCount++;
if (sel_isEqual(selector, @selector(test))) {
class_replaceMethod(object_getClass(self), @selector(test),
(IMP)test, "v#:");
return YES;
}
return NO;
}
+ (bool)resolveInstanceMethod: (SEL)selector
{
forwardingsCount++;
if (sel_isEqual(selector, @selector(test))) {
class_replaceMethod(self, @selector(test), (IMP)test, "v@:");
return YES;
}
return NO;
}
- (id)forwardingTargetForSelector: (SEL)selector
{
/*
* Do some useless calculations in as many registers as possible to
* check if the arguments are properly saved and restored.
*/
volatile register intptr_t r0 = 0, r1 = 1, r2 = 2, r3 = 3, r4 = 4,
r5 = 5, r6 = 6, r7 = 7, r8 = 8, r9 = 9, r10 = 10, r11 = 11;
volatile register double f0 = 0.5, f1 = 1.5, f2 = 2.5, f3 = 3.5,
f4 = 4.5, f5 = 5.5, f6 = 6.5, f7 = 7.5, f8 = 8.5, f9 = 9.5,
f10 = 10.5, f11 = 11.5;
double add = r0 * r1 * r2 * r3 * r4 * r5 * r6 * r7 * r8 * r9 * r10 *
r11 * f0 * f1 * f2 * f3 * f4 * f5 * f6 * f7 * f8 * f9 * f10 * f11;
if (sel_isEqual(selector, @selector(forwardingTargetTest::::)) ||
sel_isEqual(selector, @selector(forwardingTargetVarArgTest:)) ||
sel_isEqual(selector, @selector(forwardingTargetFPRetTest)) ||
sel_isEqual(selector, @selector(forwardingTargetStRetTest)))
return (id)((char *)target + (ptrdiff_t)add);
if (sel_isEqual(selector, @selector(forwardingTargetNilTest)) ||
sel_isEqual(selector, @selector(forwardingTargetNilStRetTest)))
return nil;
if (sel_isEqual(selector, @selector(forwardingTargetSelfTest)) ||
sel_isEqual(selector, @selector(forwardingTargetSelfStRetTest)))
return self;
abort();
OF_UNREACHABLE
}
@end
@implementation ForwardingTarget
- (uint32_t)forwardingTargetTest: (intptr_t)a0
: (intptr_t)a1
: (double)a2
: (double)a3
{
OFEnsure(self == target);
if (a0 != (intptr_t)0xDEADBEEF)
return 0;
if (a1 != -1)
return 0;
if (a2 != 1.25)
return 0;
if (a3 != 2.75)
return 0;
return 0x12345678;
}
- (OFString *)forwardingTargetVarArgTest: (OFConstantString *)format, ...
{
va_list args;
OFString *ret;
OFEnsure(self == target);
va_start(args, format);
ret = [[[OFString alloc] initWithFormat: format
arguments: args] autorelease];
va_end(args);
return ret;
}
- (long double)forwardingTargetFPRetTest
{
OFEnsure(self == target);
return 12345678.00006103515625;
}
- (struct StretTest)forwardingTargetStRetTest
{
struct StretTest ret = { { 0 } };
OFEnsure(self == target);
memcpy(ret.buffer, "abcdefghijklmnopqrstuvwxyz", 27);
return ret;
}
@end