blob: 409da1a96be7eecd21d5a95917acbc9fb07e59ca [file] [log] [blame]
//
// Copyright 2020 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "Service/Sources/EDOBlockObject.h"
#include <objc/message.h>
#include <objc/runtime.h>
#import "Service/Sources/EDOObject+Private.h"
@class EDOServicePort;
static NSString *const kEDOBlockObjectCoderSignatureKey = @"signature";
static NSString *const kEDOBlockObjectCoderHasStretKey = @"hasStret";
/**
* The block structure defined in ABI here:
* https://clang.llvm.org/docs/Block-ABI-Apple.html#id2
*/
struct EDOBlockHeader;
typedef void (*EDOBlockCopyHelperFunc)(struct EDOBlockHeader *dst, struct EDOBlockHeader *src);
typedef void (*EDOBlockDisposeHelperFunc)(struct EDOBlockHeader *src);
typedef struct EDOBlockDescriptor {
unsigned long int reserved;
unsigned long int size;
union {
struct {
// Optional helper functions
EDOBlockCopyHelperFunc copy_helper; // IFF (flag & 1<<25)
EDOBlockDisposeHelperFunc dispose_helper; // IFF (flag & 1<<25)
const char *signature;
} helper;
// Required ABI.2010.3.16
const char *signature; // IFF (flag & 1<<30)
};
} EDOBlockDescriptor;
/** The enums for the block flag. */
typedef NS_ENUM(int, EDOBlockFlags) {
EDOBlockFlagsHasCopyDispose = (1 << 25), // If the block has copy and dispose function pointer.
EDOBlockFlagsIsGlobal = (1 << 28), // If the block is a global block.
EDOBlockFlagsHasStret = (1 << 29), // If we should use _stret calling convention.
EDOBlockFlagsHasSignature = (1 << 30) // If the signature is filled.
};
typedef struct {
id __unsafe_unretained object;
EDOBlockFlags original_flags;
EDOBlockDescriptor *original_descriptor;
} EDOBlockCapturedVariables;
typedef struct EDOBlockHeader {
void *isa;
EDOBlockFlags flags;
int reserved;
void (*invoke)(void); // The block implementation, which is either _objc_msgForward or
// _objc_msgForward_stret to trigger message forwarding.
EDOBlockDescriptor *descriptor;
EDOBlockCapturedVariables captured_variables;
} EDOBlockHeader;
typedef NS_ENUM(int, EDOBlockFieldDescriptors) {
EDOBlockFieldIsObject = 3, // id, NSObject, __attribute__((NSObject)), block, ...
};
/* Get @c NSBlock class. */
static Class GetBlockBaseClass() {
static Class blockClass;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
blockClass = NSClassFromString(@"NSBlock");
NSCAssert(blockClass, @"Couldn't load NSBlock class.");
});
return blockClass;
}
/** Check if the @c block has struct returns. */
static BOOL HasStructReturnForBlock(id block) {
EDOBlockHeader *blockHeader = (__bridge EDOBlockHeader *)block;
return (blockHeader->flags & EDOBlockFlagsHasStret) != 0;
}
@implementation EDOBlockObject
+ (BOOL)supportsSecureCoding {
return YES;
}
+ (BOOL)isBlock:(id)object {
if ([object isProxy]) {
return NO;
}
// We use runtime primitive APIs to go through the class hierarchy in case any subclass to
// override -[isKindOfClass:] and cause unintended behaviours, i.e. OCMock.
Class blockClass = GetBlockBaseClass();
Class objClass = object_getClass(object);
while (objClass) {
if (objClass == blockClass) {
return YES;
}
objClass = class_getSuperclass(objClass);
}
return NO;
}
+ (EDOBlockObject *)EDOBlockObjectFromBlock:(id)block {
EDOBlockHeader *header = (__bridge EDOBlockHeader *)block;
#if !defined(__arm64__)
if (header->invoke == (void (*)(void))_objc_msgForward ||
header->invoke == (void (*)(void))_objc_msgForward_stret) {
return header->captured_variables.object;
}
#else
if (header->invoke == (void (*)(void))_objc_msgForward) {
return header->captured_variables.object;
}
#endif
return nil;
}
+ (char const *)signatureFromBlock:(id)block {
EDOBlockHeader *blockHeader = (__bridge EDOBlockHeader *)block;
NSAssert(blockHeader->flags & EDOBlockFlagsHasSignature, @"The block doesn't have a signature.");
if (blockHeader->flags & EDOBlockFlagsHasCopyDispose) {
return blockHeader->descriptor->helper.signature;
} else {
return blockHeader->descriptor->signature;
}
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
_signature = [aDecoder decodeObjectOfClass:[NSString class]
forKey:kEDOBlockObjectCoderSignatureKey];
_returnsStruct = [aDecoder decodeBoolForKey:kEDOBlockObjectCoderHasStretKey];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[super encodeWithCoder:aCoder];
[aCoder encodeObject:self.signature forKey:kEDOBlockObjectCoderSignatureKey];
[aCoder encodeBool:self.returnsStruct forKey:kEDOBlockObjectCoderHasStretKey];
}
// Dispose the returned descriptor using DisposeDescriptor.
static EDOBlockDescriptor *CreateDescriptorWithSignature(NSString *signature) {
EDOBlockDescriptor *newDescriptor = (EDOBlockDescriptor *)calloc(1, sizeof(EDOBlockDescriptor));
// Note that we only do dispose because our block should never have a copy handler.
newDescriptor->helper.dispose_helper = EDOBlockDisposeHelper;
newDescriptor->helper.signature = strdup([signature UTF8String]);
return newDescriptor;
}
static void DisposeDescriptor(EDOBlockDescriptor *desc) {
// Free up the memory that we allocated for the signature and our descriptor.
free((void *)desc->helper.signature);
free((void *)desc);
}
static void EDOBlockDisposeHelper(EDOBlockHeader *src) {
// Dispose is only called once when the block is being released.
// Dispose/Release our captured object.
_Block_object_dispose((__bridge void *)src->captured_variables.object, EDOBlockFieldIsObject);
// Free up the memory that we allocated for the descriptor.
DisposeDescriptor(src->descriptor);
// Reset the header back the way it was.
src->descriptor = src->captured_variables.original_descriptor;
src->flags = src->captured_variables.original_flags;
// We don't need to call the previous dispose helper because we asserted that there wasn't one
// when we created the block.
}
// When we decode it, we swap with the actual block object so the receiver can invoke on it.
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
EDOBlockCapturedVariables vars = {nil, 0, NULL};
void (^dummy)(void) = ^{
// The printf is never called, it solely exists to capture vars to associate it with the block
// so we get the proper amount of "variable" space in our block header.
printf("%p", vars.original_descriptor);
};
// Move the block from the stack to the heap.
id dummyOnHeap = [dummy copy];
EDOBlockHeader *header = (__bridge EDOBlockHeader *)dummyOnHeap;
// Verify that Apple hasn't added some fun optimizations that are copying our block in a
// weird way.
NSAssert(header->descriptor->size == sizeof(EDOBlockHeader), @"block wrong size");
// Check to make sure that Apple hasn't added copy/dispose handlers to our blocks for some reason.
// If they do, we will have to be careful to chain them in dispose.
NSAssert((header->flags & EDOBlockFlagsHasCopyDispose) == 0, @"block has copy/dispose handlers");
// Add a reference to "self" into the block.
_Block_object_assign(&header->captured_variables.object, (__bridge void *)self,
EDOBlockFieldIsObject);
// Record the original values from the header so we can restore them when we dispose the block.
header->captured_variables.original_descriptor = header->descriptor;
header->captured_variables.original_flags = header->flags;
// Create up a new descriptor with our dispose and signature.
// Note that we need to make a new descriptor because multiple blocks may be using the same
// descriptor that the compiler generates for us, and modifying it directly can mess up other
// blocks. We create our own, and then clean up all the memory in EDOBlockDisposeHelper.
EDOBlockDescriptor *newDescriptor = CreateDescriptorWithSignature(self.signature);
header->descriptor = newDescriptor;
// Add the copy/dispose/signatures flags so that the OS calls us appropriately.
header->flags |= (EDOBlockFlagsHasCopyDispose | EDOBlockFlagsHasSignature);
header->invoke = (void (*)(void))_objc_msgForward;
#if !defined(__arm64__)
if (self.returnsStruct) {
header->invoke = (void (*)(void))_objc_msgForward_stret;
}
#endif
// Swap the ownership: the unarchiver retains `self` and autoreleases the `dummyOnHeap` block.
// Here we capture self within the `dummyOnHeap` block, and replace `self` with the `dummyOnHeap`
// block. This effectively transfers the ownership of dummyOnHeap to the caller of the unarchiver.
CFBridgingRelease((__bridge void *)self);
return (__bridge id)CFBridgingRetain(dummyOnHeap);
}
- (instancetype)edo_initWithLocalObject:(id)target port:(EDOServicePort *)port {
// object is self. This does the same as self = [super initWithLocalObject:target port:port], but
// because we have the prefix to avoid the naming collisions, the compiler complains assigning
// self in a non-init method.
EDOBlockObject *object = [super edo_initWithLocalObject:target port:port];
_returnsStruct = HasStructReturnForBlock(target);
_signature = [NSString stringWithUTF8String:[EDOBlockObject signatureFromBlock:target]];
return object;
}
@end