Skip to content

Instantly share code, notes, and snippets.

@numist
Last active January 8, 2022 18:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save numist/d2e7a2b49089a8bd3c2280ad7fc38ee5 to your computer and use it in GitHub Desktop.
Save numist/d2e7a2b49089a8bd3c2280ad7fc38ee5 to your computer and use it in GitHub Desktop.
"defer { … }" implementation for Objective-C and C when __has_extension(blocks)

This all started because I was complaining about some uninitialized pointer value causing me grief1 and someone (explicitly trolling) said they always check pointers using:

int fds[2] = { -1, -1}; 
pipe(fds);
if (write(fds[0], pointer_to_check, sizeof(intptr_t)) == -1) {
    close(fds[0]);
    close(fds[1]);
    return not_valid;
} else {
    close(fds[0]);
    close(fds[1]);
    return valid;
}

In case it's not abundantly clear, you should never do this2, but of course the first thing I saw was the duplication of code responsible for managing resources, a reminder of how redundant and error-prone C can be.

A different formulation of this code might look like:

int rc, fds[2] = { -1, -1}; 
pipe(fds);
if (write(fds[0], pointer_to_check, sizeof(intptr_t)) == -1) {
    rc = not_valid;
} else {
    rc = valid;
}
close(fds[0]);
close(fds[1]);
return rc;

This reduces duplication while sacrificing code locality. I still don't love it, but I feel like it's a safer style.

Really what I want is something like Swift's defer:

int fds[2] = { -1, -1}; 
pipe(fds);
defer {
    close(fds[0]);
    close(fds[1]);
}

if (write(fds[0], pointer_to_check, sizeof(intptr_t)) == -1) {
    return not_valid;
} else {
    return valid;
}

Turned out it's not too heinous to hack together. Here it is.

Footnotes

  1. as appealing as its promise is, mmap is bad and you should never use it unless you truly need garbage collected shared memory between processes, in which case your life already sucks and I'm sorry. pread and pwrite will set errno instead of crashing your process and are not nearly as slow as you think; you should stick with them until you can measure otherwise, at which point you should investigate doing your own paging because as I just said mmap is dangerous and bad.

  2. If you have a problem with wild pointers then you also have much bigger problems that you should solve first. If you're just hacking away on code that will never run on someone else's computer you should try mincore or mach_vm_read.

#import <XCTest/XCTest.h>
#import "deference.h"
@interface Tests : XCTestCase
@end
@implementation Tests
- (void)test {
__block int rc = 0;
{
defer {
rc = 1;
};
XCTAssertEqual(rc, 0); // ✅
}
XCTAssertEqual(rc, 1); // ✅
}
@end
/* The usual TOKENPASTE and TOKENPASTE2 dance, with unique names to avoid
* collisions with any other potential shenanigans because if you're reading
* this then shenanigans are probably your thing.
*/
#define __CEEA5DCA112C4E14(x, y) x ## y
#define __D83887336EB15272(x, y) __CEEA5DCA112C4E14(x, y)
/* More naming shenanigans. This function just takes a pointer to a
* block, dereferences it, and invokes it. It's this way because
* __attribute__((__cleanup__(…))) always passes a _pointer_ to the thing as the
* cleanup functions' parameter.
*/
static void __BA7F1207D89C2F82(void (^ *pBlock)(void)) {
void (^block)(void) = *pBlock;
block();
}
/* Declare a local block variable that stores the cleanup code.
* It has three attributes:
* __unused__: because you should NEVER touch this local yourself
* __deprecated__: because you should NEVER touch this local yourself
* __cleanup__: to get its pointer passed to __BA7F1207D89C2F82 (above) when the scope ends
*/
#define defer \
void (^__D83887336EB15272(__5c07854f67ade5ee_, __LINE__))(void) __attribute__((__unused__, deprecated("hands off!"), __cleanup__(__BA7F1207D89C2F82))) = ^
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment