/*
* 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 <ctype.h>
#import "OFMethodSignature.h"
#import "OFData.h"
#import "OFString.h"
#import "OFInvalidArgumentException.h"
#import "OFInvalidFormatException.h"
#import "OFOutOfRangeException.h"
#import "macros.h"
static size_t alignmentOfEncoding(const char **type, size_t *length,
bool inStruct);
static size_t sizeOfEncoding(const char **type, size_t *length);
static size_t
alignmentOfArray(const char **type, size_t *length)
{
size_t alignment;
OFAssert(*length > 0);
(*type)++;
(*length)--;
while (*length > 0 && OFASCIIIsDigit(**type)) {
(*type)++;
(*length)--;
}
alignment = alignmentOfEncoding(type, length, true);
if (*length == 0 || **type != ']')
@throw [OFInvalidFormatException exception];
(*type)++;
(*length)--;
return alignment;
}
static size_t
alignmentOfStruct(const char **type, size_t *length)
{
size_t alignment = 0;
#if defined(OF_POWERPC) && defined(OF_MACOS)
bool first = true;
#endif
OFAssert(*length > 0);
(*type)++;
(*length)--;
/* Skip name */
while (*length > 0 && **type != '=') {
(*type)++;
(*length)--;
}
if (*length == 0)
@throw [OFInvalidFormatException exception];
/* Skip '=' */
(*type)++;
(*length)--;
while (*length > 0 && **type != '}') {
size_t fieldAlignment = alignmentOfEncoding(type, length, true);
#if defined(OF_POWERPC) && defined(OF_MACOS)
if (!first && fieldAlignment > 4)
fieldAlignment = 4;
first = false;
#endif
if (fieldAlignment > alignment)
alignment = fieldAlignment;
}
if (*length == 0 || **type != '}')
@throw [OFInvalidFormatException exception];
(*type)++;
(*length)--;
return alignment;
}
static size_t
alignmentOfUnion(const char **type, size_t *length)
{
size_t alignment = 0;
OFAssert(*length > 0);
(*type)++;
(*length)--;
/* Skip name */
while (*length > 0 && **type != '=') {
(*type)++;
(*length)--;
}
if (*length == 0)
@throw [OFInvalidFormatException exception];
/* Skip '=' */
(*type)++;
(*length)--;
while (*length > 0 && **type != ')') {
size_t fieldAlignment = alignmentOfEncoding(type, length, true);
if (fieldAlignment > alignment)
alignment = fieldAlignment;
}
if (*length == 0 || **type != ')')
@throw [OFInvalidFormatException exception];
(*type)++;
(*length)--;
return alignment;
}
static size_t
#if defined(__clang__) && __has_attribute(__optnone__) && \
__clang_major__ == 3 && __clang_minor__ <= 7
/* Work around an ICE in Clang 3.7.0 on Windows/x86 */
__attribute__((__optnone__))
#endif
alignmentOfEncoding(const char **type, size_t *length, bool inStruct)
{
size_t alignment;
if (*length == 0)
@throw [OFInvalidFormatException exception];
if (**type == 'r') {
(*type)++;
(*length)--;
if (*length == 0)
@throw [OFInvalidFormatException exception];
}
switch (**type) {
case 'c':
case 'C':
alignment = OF_ALIGNOF(char);
break;
case 'i':
case 'I':
alignment = OF_ALIGNOF(int);
break;
case 's':
case 'S':
alignment = OF_ALIGNOF(short);
break;
case 'l':
case 'L':
alignment = OF_ALIGNOF(long);
break;
case 'q':
case 'Q':
#if defined(OF_X86) && !defined(OF_WINDOWS)
if (inStruct)
alignment = 4;
else
#endif
alignment = OF_ALIGNOF(long long);
break;
#ifdef __SIZEOF_INT128__
case 't':
case 'T':
alignment = __extension__ OF_ALIGNOF(__int128);
break;
#endif
case 'f':
alignment = OF_ALIGNOF(float);
break;
case 'd':
#if defined(OF_X86) && !defined(OF_WINDOWS)
if (inStruct)
alignment = 4;
else
#endif
alignment = OF_ALIGNOF(double);
break;
case 'D':
#if defined(OF_X86) && !defined(OF_WINDOWS)
if (inStruct)
alignment = 4;
else
#endif
alignment = OF_ALIGNOF(long double);
break;
case 'B':
alignment = OF_ALIGNOF(_Bool);
break;
case 'v':
alignment = 0;
break;
case '*':
alignment = OF_ALIGNOF(char *);
break;
case '@':
alignment = OF_ALIGNOF(id);
break;
case '#':
alignment = OF_ALIGNOF(Class);
break;
case ':':
alignment = OF_ALIGNOF(SEL);
break;
case '[':
return alignmentOfArray(type, length);
case '{':
return alignmentOfStruct(type, length);
case '(':
return alignmentOfUnion(type, length);
case '^':
/* Just to skip over the rest */
(*type)++;
(*length)--;
alignmentOfEncoding(type, length, false);
return OF_ALIGNOF(void *);
#ifndef __STDC_NO_COMPLEX__
case 'j':
(*type)++;
(*length)--;
if (*length == 0)
@throw [OFInvalidFormatException exception];
switch (**type) {
case 'f':
alignment = OF_ALIGNOF(float _Complex);
break;
case 'd':
# if defined(OF_X86) && !defined(OF_WINDOWS)
if (inStruct)
alignment = 4;
else
# endif
alignment = OF_ALIGNOF(double _Complex);
break;
case 'D':
alignment = OF_ALIGNOF(long double _Complex);
break;
default:
@throw [OFInvalidFormatException exception];
}
break;
#endif
default:
@throw [OFInvalidFormatException exception];
}
(*type)++;
(*length)--;
return alignment;
}
static size_t
sizeOfArray(const char **type, size_t *length)
{
size_t count = 0;
size_t size;
OFAssert(*length > 0);
(*type)++;
(*length)--;
while (*length > 0 && OFASCIIIsDigit(**type)) {
count = count * 10 + **type - '0';
(*type)++;
(*length)--;
}
if (count == 0)
@throw [OFInvalidFormatException exception];
size = sizeOfEncoding(type, length);
if (*length == 0 || **type != ']')
@throw [OFInvalidFormatException exception];
(*type)++;
(*length)--;
if (SIZE_MAX / count < size)
@throw [OFOutOfRangeException exception];
return count * size;
}
static size_t
sizeOfStruct(const char **type, size_t *length)
{
size_t size = 0;
const char *typeCopy = *type;
size_t lengthCopy = *length;
size_t alignment = alignmentOfStruct(&typeCopy, &lengthCopy);
#if defined(OF_POWERPC) && defined(OF_MACOS)
bool first = true;
#endif
OFAssert(*length > 0);
(*type)++;
(*length)--;
/* Skip name */
while (*length > 0 && **type != '=') {
(*type)++;
(*length)--;
}
if (*length == 0)
@throw [OFInvalidFormatException exception];
/* Skip '=' */
(*type)++;
(*length)--;
while (*length > 0 && **type != '}') {
size_t fieldSize, fieldAlignment;
typeCopy = *type;
lengthCopy = *length;
fieldSize = sizeOfEncoding(type, length);
fieldAlignment = alignmentOfEncoding(&typeCopy, &lengthCopy,
true);
#if defined(OF_POWERPC) && defined(OF_MACOS)
if (!first && fieldAlignment > 4)
fieldAlignment = 4;
first = false;
#endif
if (size % fieldAlignment != 0) {
size_t padding =
fieldAlignment - (size % fieldAlignment);
if (SIZE_MAX - size < padding)
@throw [OFOutOfRangeException exception];
size += padding;
}
if (SIZE_MAX - size < fieldSize)
@throw [OFOutOfRangeException exception];
size += fieldSize;
}
if (*length == 0 || **type != '}')
@throw [OFInvalidFormatException exception];
(*type)++;
(*length)--;
if (size % alignment != 0) {
size_t padding = alignment - (size % alignment);
if (SIZE_MAX - size < padding)
@throw [OFOutOfRangeException exception];
size += padding;
}
return size;
}
static size_t
sizeOfUnion(const char **type, size_t *length)
{
size_t size = 0;
OFAssert(*length > 0);
(*type)++;
(*length)--;
/* Skip name */
while (*length > 0 && **type != '=') {
(*type)++;
(*length)--;
}
if (*length == 0)
@throw [OFInvalidFormatException exception];
/* Skip '=' */
(*type)++;
(*length)--;
while (*length > 0 && **type != ')') {
size_t fieldSize = sizeOfEncoding(type, length);
if (fieldSize > size)
size = fieldSize;
}
if (*length == 0 || **type != ')')
@throw [OFInvalidFormatException exception];
(*type)++;
(*length)--;
return size;
}
static size_t
#if defined(__clang__) && __has_attribute(__optnone__) && \
__clang_major__ == 3 && __clang_minor__ <= 7
/* Work around an ICE in Clang 3.7.0 on Windows/x86 */
__attribute__((__optnone__))
#endif
sizeOfEncoding(const char **type, size_t *length)
{
size_t size;
if (*length == 0)
@throw [OFInvalidFormatException exception];
if (**type == 'r') {
(*type)++;
(*length)--;
if (*length == 0)
@throw [OFInvalidFormatException exception];
}
switch (**type) {
case 'c':
case 'C':
size = sizeof(char);
break;
case 'i':
case 'I':
size = sizeof(int);
break;
case 's':
case 'S':
size = sizeof(short);
break;
case 'l':
case 'L':
size = sizeof(long);
break;
case 'q':
case 'Q':
size = sizeof(long long);
break;
#ifdef __SIZEOF_INT128__
case 't':
case 'T':
size = __extension__ sizeof(__int128);
break;
#endif
case 'f':
size = sizeof(float);
break;
case 'd':
size = sizeof(double);
break;
case 'D':
size = sizeof(long double);
break;
case 'B':
size = sizeof(_Bool);
break;
case 'v':
size = 0;
break;
case '*':
size = sizeof(char *);
break;
case '@':
size = sizeof(id);
break;
case '#':
size = sizeof(Class);
break;
case ':':
size = sizeof(SEL);
break;
case '[':
return sizeOfArray(type, length);
case '{':
return sizeOfStruct(type, length);
case '(':
return sizeOfUnion(type, length);
case '^':
/* Just to skip over the rest */
(*type)++;
(*length)--;
sizeOfEncoding(type, length);
return sizeof(void *);
#ifndef __STDC_NO_COMPLEX__
case 'j':
(*type)++;
(*length)--;
if (*length == 0)
@throw [OFInvalidFormatException exception];
switch (**type) {
case 'f':
size = sizeof(float _Complex);
break;
case 'd':
size = sizeof(double _Complex);
break;
case 'D':
size = sizeof(long double _Complex);
break;
default:
@throw [OFInvalidFormatException exception];
}
break;
#endif
default:
@throw [OFInvalidFormatException exception];
}
(*type)++;
(*length)--;
return size;
}
size_t
OFSizeOfTypeEncoding(const char *type)
{
size_t length = strlen(type);
size_t ret = sizeOfEncoding(&type, &length);
if (length > 0)
@throw [OFInvalidFormatException exception];
return ret;
}
size_t
OFAlignmentOfTypeEncoding(const char *type)
{
size_t length = strlen(type);
size_t ret = alignmentOfEncoding(&type, &length, false);
if (length > 0)
@throw [OFInvalidFormatException exception];
return ret;
}
@implementation OFMethodSignature
+ (instancetype)signatureWithObjCTypes: (const char*)types
{
return [[[self alloc] initWithObjCTypes: types] autorelease];
}
- (instancetype)init
{
OF_INVALID_INIT_METHOD
}
- (instancetype)initWithObjCTypes: (const char *)types
{
self = [super init];
@try {
size_t length;
const char *last;
if (types == NULL)
@throw [OFInvalidArgumentException exception];
length = strlen(types);
if (length == 0)
@throw [OFInvalidFormatException exception];
_types = OFAllocMemory(length + 1, 1);
memcpy(_types, types, length);
_typesPointers = [[OFMutableData alloc]
initWithItemSize: sizeof(char *)];
_offsets = [[OFMutableData alloc]
initWithItemSize: sizeof(size_t)];
last = _types;
for (size_t i = 0; i < length; i++) {
if (OFASCIIIsDigit(_types[i])) {
size_t offset = _types[i] - '0';
if (last == _types + i)
@throw [OFInvalidFormatException
exception];
_types[i] = '\0';
[_typesPointers addItem: &last];
i++;
for (; i < length &&
OFASCIIIsDigit(_types[i]); i++)
offset = offset * 10 + _types[i] - '0';
[_offsets addItem: &offset];
last = _types + i;
i--;
} else if (_types[i] == '{') {
size_t depth = 0;
for (; i < length; i++) {
if (_types[i] == '{')
depth++;
else if (_types[i] == '}') {
if (--depth == 0)
break;
}
}
if (depth != 0)
@throw [OFInvalidFormatException
exception];
} else if (_types[i] == '(') {
size_t depth = 0;
for (; i < length; i++) {
if (_types[i] == '(')
depth++;
else if (_types[i] == ')') {
if (--depth == 0)
break;
}
}
if (depth != 0)
@throw [OFInvalidFormatException
exception];
}
}
if (last < _types + length)
@throw [OFInvalidFormatException exception];
} @catch (id e) {
[self release];
@throw e;
}
return self;
}
- (void)dealloc
{
OFFreeMemory(_types);
[_typesPointers release];
[_offsets release];
[super dealloc];
}
- (size_t)numberOfArguments
{
return _typesPointers.count - 1;
}
- (const char *)methodReturnType
{
return *(const char **)_typesPointers.firstItem;
}
- (size_t)frameLength
{
return *(size_t *)_offsets.firstItem;
}
- (const char *)argumentTypeAtIndex: (size_t)idx
{
return *(const char **)[_typesPointers itemAtIndex: idx + 1];
}
- (size_t)argumentOffsetAtIndex: (size_t)idx
{
return *(size_t *)[_offsets itemAtIndex: idx + 1];
}
@end