objc-runtime.mm   [plain text]


/*
 * Copyright (c) 1999-2007 Apple Inc.  All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/***********************************************************************
* objc-runtime.m
* Copyright 1988-1996, NeXT Software, Inc.
* Author:	s. naroff
*
**********************************************************************/



/***********************************************************************
* Imports.
**********************************************************************/

#include "objc-private.h"
#include "objc-loadmethod.h"
#include "message.h"

OBJC_EXPORT Class getOriginalClassForPosingClass(Class);


/***********************************************************************
* Exports.
**********************************************************************/

// Settings from environment variables
#define OPTION(var, env, help) bool var = false;
#include "objc-env.h"
#undef OPTION

struct option_t {
    bool* var;
    const char *env;
    const char *help;
    size_t envlen;
};

const option_t Settings[] = {
#define OPTION(var, env, help) option_t{&var, #env, help, strlen(#env)}, 
#include "objc-env.h"
#undef OPTION
};


// objc's key for pthread_getspecific
static tls_key_t _objc_pthread_key;

// Selectors
SEL SEL_load = NULL;
SEL SEL_initialize = NULL;
SEL SEL_resolveInstanceMethod = NULL;
SEL SEL_resolveClassMethod = NULL;
SEL SEL_cxx_construct = NULL;
SEL SEL_cxx_destruct = NULL;
SEL SEL_retain = NULL;
SEL SEL_release = NULL;
SEL SEL_autorelease = NULL;
SEL SEL_retainCount = NULL;
SEL SEL_alloc = NULL;
SEL SEL_allocWithZone = NULL;
SEL SEL_dealloc = NULL;
SEL SEL_copy = NULL;
SEL SEL_new = NULL;
SEL SEL_forwardInvocation = NULL;
SEL SEL_tryRetain = NULL;
SEL SEL_isDeallocating = NULL;
SEL SEL_retainWeakReference = NULL;
SEL SEL_allowsWeakReference = NULL;


header_info *FirstHeader = 0;  // NULL means empty list
header_info *LastHeader  = 0;  // NULL means invalid; recompute it
int HeaderCount = 0;


/***********************************************************************
* objc_getClass.  Return the id of the named class.  If the class does
* not exist, call _objc_classLoader and then objc_classHandler, either of 
* which may create a new class.
* Warning: doesn't work if aClassName is the name of a posed-for class's isa!
**********************************************************************/
Class objc_getClass(const char *aClassName)
{
    if (!aClassName) return Nil;

    // NO unconnected, YES class handler
    return look_up_class(aClassName, NO, YES);
}


/***********************************************************************
* objc_getRequiredClass.  
* Same as objc_getClass, but kills the process if the class is not found. 
* This is used by ZeroLink, where failing to find a class would be a 
* compile-time link error without ZeroLink.
**********************************************************************/
Class objc_getRequiredClass(const char *aClassName)
{
    Class cls = objc_getClass(aClassName);
    if (!cls) _objc_fatal("link error: class '%s' not found.", aClassName);
    return cls;
}


/***********************************************************************
* objc_lookUpClass.  Return the id of the named class.
* If the class does not exist, call _objc_classLoader, which may create 
* a new class.
*
* Formerly objc_getClassWithoutWarning ()
**********************************************************************/
Class objc_lookUpClass(const char *aClassName)
{
    if (!aClassName) return Nil;

    // NO unconnected, NO class handler
    return look_up_class(aClassName, NO, NO);
}


/***********************************************************************
* objc_getMetaClass.  Return the id of the meta class the named class.
* Warning: doesn't work if aClassName is the name of a posed-for class's isa!
**********************************************************************/
Class objc_getMetaClass(const char *aClassName)
{
    Class cls;

    if (!aClassName) return Nil;

    cls = objc_getClass (aClassName);
    if (!cls)
    {
        _objc_inform ("class `%s' not linked into application", aClassName);
        return Nil;
    }

    return cls->ISA();
}


/***********************************************************************
* appendHeader.  Add a newly-constructed header_info to the list. 
**********************************************************************/
void appendHeader(header_info *hi)
{
    // Add the header to the header list. 
    // The header is appended to the list, to preserve the bottom-up order.
    HeaderCount++;
    hi->setNext(NULL);
    if (!FirstHeader) {
        // list is empty
        FirstHeader = LastHeader = hi;
    } else {
        if (!LastHeader) {
            // list is not empty, but LastHeader is invalid - recompute it
            LastHeader = FirstHeader;
            while (LastHeader->getNext()) LastHeader = LastHeader->getNext();
        }
        // LastHeader is now valid
        LastHeader->setNext(hi);
        LastHeader = hi;
    }
}


/***********************************************************************
* removeHeader
* Remove the given header from the header list.
* FirstHeader is updated. 
* LastHeader is set to NULL. Any code that uses LastHeader must 
* detect this NULL and recompute LastHeader by traversing the list.
**********************************************************************/
void removeHeader(header_info *hi)
{
    header_info *prev = NULL;
    header_info *current = NULL;

    for (current = FirstHeader; current != NULL; current = current->getNext()) {
        if (current == hi) {
            header_info *deadHead = current;

            // Remove from the linked list.
            if (prev)
                prev->setNext(current->getNext());
            else
                FirstHeader = current->getNext(); // no prev so removing head
            
            // Update LastHeader if necessary.
            if (LastHeader == deadHead) {
                LastHeader = NULL;  // will be recomputed next time it's used
            }

            HeaderCount--;
            break;
        }
        prev = current;
    }
}


/***********************************************************************
* environ_init
* Read environment variables that affect the runtime.
* Also print environment variable help, if requested.
**********************************************************************/
void environ_init(void) 
{
    if (issetugid()) {
        // All environment variables are silently ignored when setuid or setgid
        // This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves.
        return;
    } 

    bool PrintHelp = false;
    bool PrintOptions = false;
    bool maybeMallocDebugging = false;

    // Scan environ[] directly instead of calling getenv() a lot.
    // This optimizes the case where none are set.
    for (char **p = *_NSGetEnviron(); *p != nil; p++) {
        if (0 == strncmp(*p, "Malloc", 6)  ||  0 == strncmp(*p, "DYLD", 4)  ||  
            0 == strncmp(*p, "NSZombiesEnabled", 16))
        {
            maybeMallocDebugging = true;
        }

        if (0 != strncmp(*p, "OBJC_", 5)) continue;
        
        if (0 == strncmp(*p, "OBJC_HELP=", 10)) {
            PrintHelp = true;
            continue;
        }
        if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) {
            PrintOptions = true;
            continue;
        }
        
        const char *value = strchr(*p, '=');
        if (!*value) continue;
        value++;
        
        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];
            if ((size_t)(value - *p) == 1+opt->envlen  &&  
                0 == strncmp(*p, opt->env, opt->envlen))
            {
                *opt->var = (0 == strcmp(value, "YES"));
                break;
            }
        }            
    }

    // Special case: enable some autorelease pool debugging 
    // when some malloc debugging is enabled 
    // and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO.
    if (maybeMallocDebugging) {
        const char *insert = getenv("DYLD_INSERT_LIBRARIES");
        const char *zombie = getenv("NSZombiesEnabled");
        const char *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION");
        if ((getenv("MallocStackLogging")
             || getenv("MallocStackLoggingNoCompact")
             || (zombie && (*zombie == 'Y' || *zombie == 'y'))
             || (insert && strstr(insert, "libgmalloc")))
            &&
            (!pooldebug || 0 == strcmp(pooldebug, "YES")))
        {
            DebugPoolAllocation = true;
        }
    }

    // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    if (PrintHelp  ||  PrintOptions) {
        if (PrintHelp) {
            _objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
            _objc_inform("OBJC_HELP: describe available environment variables");
            if (PrintOptions) {
                _objc_inform("OBJC_HELP is set");
            }
            _objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
        }
        if (PrintOptions) {
            _objc_inform("OBJC_PRINT_OPTIONS is set");
        }

        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];            
            if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
            if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
        }
    }
}


/***********************************************************************
* logReplacedMethod
* OBJC_PRINT_REPLACED_METHODS implementation
**********************************************************************/
void 
logReplacedMethod(const char *className, SEL s, 
                  bool isMeta, const char *catName, 
                  IMP oldImp, IMP newImp)
{
    const char *oldImage = "??";
    const char *newImage = "??";

    // Silently ignore +load replacement because category +load is special
    if (s == SEL_load) return;

#if TARGET_OS_WIN32
    // don't know dladdr()/dli_fname equivalent
#else
    Dl_info dl;

    if (dladdr((void*)oldImp, &dl)  &&  dl.dli_fname) oldImage = dl.dli_fname;
    if (dladdr((void*)newImp, &dl)  &&  dl.dli_fname) newImage = dl.dli_fname;
#endif
    
    _objc_inform("REPLACED: %c[%s %s]  %s%s  (IMP was %p (%s), now %p (%s))",
                 isMeta ? '+' : '-', className, sel_getName(s), 
                 catName ? "by category " : "", catName ? catName : "", 
                 oldImp, oldImage, newImp, newImage);
}



/***********************************************************************
* objc_setMultithreaded.
**********************************************************************/
void objc_setMultithreaded (BOOL flag)
{
    OBJC_WARN_DEPRECATED;

    // Nothing here. Thread synchronization in the runtime is always active.
}


/***********************************************************************
* _objc_fetch_pthread_data
* Fetch objc's pthread data for this thread.
* If the data doesn't exist yet and create is NO, return NULL.
* If the data doesn't exist yet and create is YES, allocate and return it.
**********************************************************************/
_objc_pthread_data *_objc_fetch_pthread_data(bool create)
{
    _objc_pthread_data *data;

    data = (_objc_pthread_data *)tls_get(_objc_pthread_key);
    if (!data  &&  create) {
        data = (_objc_pthread_data *)
            calloc(1, sizeof(_objc_pthread_data));
        tls_set(_objc_pthread_key, data);
    }

    return data;
}


/***********************************************************************
* _objc_pthread_destroyspecific
* Destructor for objc's per-thread data.
* arg shouldn't be NULL, but we check anyway.
**********************************************************************/
extern void _destroyInitializingClassList(struct _objc_initializing_classes *list);
void _objc_pthread_destroyspecific(void *arg)
{
    _objc_pthread_data *data = (_objc_pthread_data *)arg;
    if (data != NULL) {
        _destroyInitializingClassList(data->initializingClasses);
        _destroySyncCache(data->syncCache);
        _destroyAltHandlerList(data->handlerList);
        for (int i = 0; i < (int)countof(data->printableNames); i++) {
            if (data->printableNames[i]) {
                free(data->printableNames[i]);  
            }
        }

        // add further cleanup here...

        free(data);
    }
}


void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
    _objc_pthread_key = TLS_DIRECT_KEY;
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}


/***********************************************************************
* _objcInit
* Former library initializer. This function is now merely a placeholder 
* for external callers. All runtime initialization has now been moved 
* to map_images() and _objc_init.
**********************************************************************/
void _objcInit(void)
{
    // do nothing
}


/***********************************************************************
* objc_setForwardHandler
**********************************************************************/

#if !__OBJC2__

// Default forward handler (nil) goes to forward:: dispatch.
void *_objc_forward_handler = nil;
void *_objc_forward_stret_handler = nil;

#else

// Default forward handler halts the process.
__attribute__((noreturn)) void 
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

#if SUPPORT_STRET
struct stret { int i[100]; };
__attribute__((noreturn)) struct stret 
objc_defaultForwardStretHandler(id self, SEL sel)
{
    objc_defaultForwardHandler(self, sel);
}
void *_objc_forward_stret_handler = (void*)objc_defaultForwardStretHandler;
#endif

#endif

void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
    _objc_forward_handler = fwd;
#if SUPPORT_STRET
    _objc_forward_stret_handler = fwd_stret;
#endif
}


#if !__OBJC2__
// GrP fixme
extern "C" Class _objc_getOrigClass(const char *name);
#endif
const char *class_getImageName(Class cls)
{
#if TARGET_OS_WIN32
    TCHAR *szFileName;
    DWORD charactersCopied;
    Class origCls;
    HMODULE classModule;
    bool res;
#endif
    if (!cls) return NULL;

#if !__OBJC2__
    cls = _objc_getOrigClass(cls->demangledName());
#endif
#if TARGET_OS_WIN32
    charactersCopied = 0;
    szFileName = malloc(MAX_PATH * sizeof(TCHAR));
    
    origCls = objc_getOrigClass(cls->demangledName());
    classModule = NULL;
    res = GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)origCls, &classModule);
    if (res && classModule) {
        charactersCopied = GetModuleFileName(classModule, szFileName, MAX_PATH * sizeof(TCHAR));
    }
    if (classModule) FreeLibrary(classModule);
    if (charactersCopied) {
        return (const char *)szFileName;
    } else {
        free(szFileName);
    }
    return NULL;
#else
    return dyld_image_path_containing_address(cls);
#endif
}


const char **objc_copyImageNames(unsigned int *outCount)
{
    header_info *hi;
    int count = 0;
    int max = HeaderCount;
#if TARGET_OS_WIN32
    const TCHAR **names = (const TCHAR **)calloc(max+1, sizeof(TCHAR *));
#else
    const char **names = (const char **)calloc(max+1, sizeof(char *));
#endif
    
    for (hi = FirstHeader; hi != NULL && count < max; hi = hi->getNext()) {
#if TARGET_OS_WIN32
        if (hi->moduleName) {
            names[count++] = hi->moduleName;
        }
#else
        const char *fname = hi->fname();
        if (fname) {
            names[count++] = fname;
        }
#endif
    }
    names[count] = NULL;
    
    if (count == 0) {
        // Return NULL instead of empty list if there are no images
        free((void *)names);
        names = NULL;
    }

    if (outCount) *outCount = count;
    return names;
}


/**********************************************************************
*
**********************************************************************/
const char ** 
objc_copyClassNamesForImage(const char *image, unsigned int *outCount)
{
    header_info *hi;

    if (!image) {
        if (outCount) *outCount = 0;
        return NULL;
    }

    // Find the image.
    for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
#if TARGET_OS_WIN32
        if (0 == wcscmp((TCHAR *)image, hi->moduleName)) break;
#else
        if (0 == strcmp(image, hi->fname())) break;
#endif
    }
    
    if (!hi) {
        if (outCount) *outCount = 0;
        return NULL;
    }

    return _objc_copyClassNamesForImage(hi, outCount);
}
	

/**********************************************************************
* Fast Enumeration Support
**********************************************************************/

static void (*enumerationMutationHandler)(id);

/**********************************************************************
* objc_enumerationMutation
* called by compiler when a mutation is detected during foreach iteration
**********************************************************************/
void objc_enumerationMutation(id object) {
    if (enumerationMutationHandler == nil) {
        _objc_fatal("mutation detected during 'for(... in ...)'  enumeration of object %p.", (void*)object);
    }
    (*enumerationMutationHandler)(object);
}


/**********************************************************************
* objc_setEnumerationMutationHandler
* an entry point to customize mutation error handing
**********************************************************************/
void objc_setEnumerationMutationHandler(void (*handler)(id)) {
    enumerationMutationHandler = handler;
}


/**********************************************************************
* Associative Reference Support
**********************************************************************/

id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}


void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}


void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}



#if SUPPORT_GC_COMPAT

#include <mach-o/fat.h>

// GC preflight for an app executable.

enum GCness {
    WithGC = 1, 
    WithoutGC = 0, 
    Error = -1
};

// Overloaded template wrappers around clang's overflow-checked arithmetic.

template <typename T> bool uadd_overflow(T x, T y, T* sum);
template <typename T> bool usub_overflow(T x, T y, T* diff);
template <typename T> bool umul_overflow(T x, T y, T* prod);

template <typename T> bool sadd_overflow(T x, T y, T* sum);
template <typename T> bool ssub_overflow(T x, T y, T* diff);
template <typename T> bool smul_overflow(T x, T y, T* prod);

template <> bool uadd_overflow(unsigned x, unsigned y, unsigned* sum) { return __builtin_uadd_overflow(x, y, sum); }
template <> bool uadd_overflow(unsigned long x, unsigned long y, unsigned long* sum) { return __builtin_uaddl_overflow(x, y, sum); }
template <> bool uadd_overflow(unsigned long long x, unsigned long long y, unsigned long long* sum) { return __builtin_uaddll_overflow(x, y, sum); }

template <> bool usub_overflow(unsigned x, unsigned y, unsigned* diff) { return __builtin_usub_overflow(x, y, diff); }
template <> bool usub_overflow(unsigned long x, unsigned long y, unsigned long* diff) { return __builtin_usubl_overflow(x, y, diff); }
template <> bool usub_overflow(unsigned long long x, unsigned long long y, unsigned long long* diff) { return __builtin_usubll_overflow(x, y, diff); }

template <> bool umul_overflow(unsigned x, unsigned y, unsigned* prod) { return __builtin_umul_overflow(x, y, prod); }
template <> bool umul_overflow(unsigned long x, unsigned long y, unsigned long* prod) { return __builtin_umull_overflow(x, y, prod); }
template <> bool umul_overflow(unsigned long long x, unsigned long long y, unsigned long long* prod) { return __builtin_umulll_overflow(x, y, prod); }

template <> bool sadd_overflow(signed x, signed y, signed* sum) { return __builtin_sadd_overflow(x, y, sum); }
template <> bool sadd_overflow(signed long x, signed long y, signed long* sum) { return __builtin_saddl_overflow(x, y, sum); }
template <> bool sadd_overflow(signed long long x, signed long long y, signed long long* sum) { return __builtin_saddll_overflow(x, y, sum); }

template <> bool ssub_overflow(signed x, signed y, signed* diff) { return __builtin_ssub_overflow(x, y, diff); }
template <> bool ssub_overflow(signed long x, signed long y, signed long* diff) { return __builtin_ssubl_overflow(x, y, diff); }
template <> bool ssub_overflow(signed long long x, signed long long y, signed long long* diff) { return __builtin_ssubll_overflow(x, y, diff); }

template <> bool smul_overflow(signed x, signed y, signed* prod) { return __builtin_smul_overflow(x, y, prod); }
template <> bool smul_overflow(signed long x, signed long y, signed long* prod) { return __builtin_smull_overflow(x, y, prod); }
template <> bool smul_overflow(signed long long x, signed long long y, signed long long* prod) { return __builtin_smulll_overflow(x, y, prod); }


// Range-checking subview of a file.
class FileSlice {
    int fd;
    uint64_t sliceOffset;
    uint64_t sliceSize;

public:
    FileSlice() : fd(-1), sliceOffset(0), sliceSize(0) { }

    FileSlice(int newfd, uint64_t newOffset, uint64_t newSize) 
        : fd(newfd) , sliceOffset(newOffset) , sliceSize(newSize) { }

    // Read bytes from this slice. 
    // Returns YES if all bytes were read successfully.
    bool pread(void *buf, uint64_t readSize, uint64_t readOffset = 0) {
        uint64_t readEnd;
        if (uadd_overflow(readOffset, readSize, &readEnd)) return NO;
        if (readEnd > sliceSize) return NO;

        uint64_t preadOffset;
        if (uadd_overflow(sliceOffset, readOffset, &preadOffset)) return NO;

        int64_t readed = ::pread(fd, buf, (size_t)readSize, preadOffset);
        if (readed < 0  ||  (uint64_t)readed != readSize) return NO;
        return YES;
    }

    // Create a new slice that is a subset of this slice.
    // Returnes YES if successful.
    bool slice(uint64_t newOffset, uint64_t newSize, FileSlice& result) {
        // fixme arithmetic overflow
        uint64_t newEnd;
        if (uadd_overflow(newOffset, newSize, &newEnd)) return NO;
        if (newEnd > sliceSize) return NO;

        if (uadd_overflow(sliceOffset, newOffset, &result.sliceOffset)) {
            return NO;
        }
        result.sliceSize = newSize;
        result.fd = fd;
        return YES;
    }

    // Shorten this slice in place by removing a range from the start.
    bool advance(uint64_t distance) {
        if (distance > sliceSize) return NO;
        if (uadd_overflow(sliceOffset, distance, &sliceOffset)) return NO;
        if (usub_overflow(sliceSize, distance, &sliceSize)) return NO;
        return YES;
    }
};


// Arch32 and Arch64 are used to specialize sliceRequiresGC()
// to interrogate old-ABI i386 and new-ABI x86_64 files.

struct Arch32 {
    using mh_t = struct mach_header;
    using segment_command_t = struct segment_command;
    using section_t = struct section;

    enum : cpu_type_t { cputype = CPU_TYPE_X86 };
    enum : int { segment_cmd = LC_SEGMENT };

    static bool isObjCSegment(const char *segname) {
        return segnameEquals(segname, "__OBJC");
    }

    static bool isImageInfoSection(const char *sectname) {
        return sectnameEquals(sectname, "__image_info");
    }

    static bool countClasses(FileSlice file, section_t& sect, 
                             int& classCount, int& classrefCount)
    {
        if (sectnameEquals(sect.sectname, "__cls_refs")) {
            classrefCount += sect.size / 4;
        }
        else if (sectnameEquals(sect.sectname, "__module_info")) {
            struct module_t {
                uint32_t version;
                uint32_t size;
                uint32_t name;    // not bound
                uint32_t symtab;  // not bound
            };
            size_t mod_count = sect.size / sizeof(module_t);
            if (mod_count == 0) {
                // no classes defined
            } else if (mod_count > 1) {
                // AppleScriptObjC apps only have one module.
                // Disqualify this app by setting classCount to non-zero.
                // We don't actually need an accurate count.
                classCount = 1;
            } else if (mod_count == 1) {
                FileSlice moduleSlice;
                if (!file.slice(sect.offset, sect.size, moduleSlice)) return NO;
                module_t module;
                if (!moduleSlice.pread(&module, sizeof(module))) return NO;
                if (module.symtab) {
                    // AppleScriptObjC apps only have a module with no symtab.
                    // Disqualify this app by setting classCount to non-zero.
                    // We don't actually need an accurate count.
                    classCount = 1;
                }
            }
            
        }
        return YES;
    }

};

struct Arch64 {
    using mh_t = struct mach_header_64;
    using segment_command_t = struct segment_command_64;
    using section_t = struct section_64;

    enum : cpu_type_t { cputype = CPU_TYPE_X86_64 };
    enum : int { segment_cmd = LC_SEGMENT_64 };

    static bool isObjCSegment(const char *segname) {
        return 
            segnameEquals(segname, "__DATA")  ||  
            segnameEquals(segname, "__DATA_CONST")  ||  
            segnameEquals(segname, "__DATA_DIRTY");
    }

    static bool isImageInfoSection(const char *sectname) {
        return sectnameEquals(sectname, "__objc_imageinfo");
    }

    static bool countClasses(FileSlice, section_t& sect, 
                             int& classCount, int& classrefCount)
    {
        if (sectnameEquals(sect.sectname, "__objc_classlist")) {
            classCount += sect.size / 8;
        }
        else if (sectnameEquals(sect.sectname, "__objc_classrefs")) {
            classrefCount += sect.size / 8;
        }
        return YES;
    }
};


#define SANE_HEADER_SIZE (32*1024)

template <typename Arch>
static int sliceRequiresGC(typename Arch::mh_t mh, FileSlice file)
{
    // We assume there is only one arch per pointer size that can support GC.
    // (i386 and x86_64)
    if (mh.cputype != Arch::cputype) return 0;

    // We only check the main executable.
    if (mh.filetype != MH_EXECUTE) return 0;

    // Look for ObjC segment.
    // Look for AppleScriptObjC linkage.
    FileSlice cmds;
    if (!file.slice(sizeof(mh), mh.sizeofcmds, cmds)) return Error;

    // Exception: Some AppleScriptObjC apps built for GC can run without GC.
    // 1. executable defines no classes
    // 2. executable references NSBundle only
    // 3. executable links to AppleScriptObjC.framework
    // Note that shouldRejectGCApp() also knows about this.
    bool wantsGC = NO;
    bool linksToAppleScriptObjC = NO;
    int classCount = 0;
    int classrefCount = 0;

    // Disallow abusively-large executables that could hang this checker.
    // dyld performs similar checks (MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE)
    if (mh.sizeofcmds > SANE_HEADER_SIZE) return Error;
    if (mh.ncmds > mh.sizeofcmds / sizeof(struct load_command)) return Error;

    for (uint32_t cmdindex = 0; cmdindex < mh.ncmds; cmdindex++) {
        struct load_command lc;
        if (!cmds.pread(&lc, sizeof(lc))) return Error;

        // Disallow abusively-small load commands that could hang this checker.
        // dyld performs a similar check.
        if (lc.cmdsize < sizeof(lc)) return Error;

        if (lc.cmd == LC_LOAD_DYLIB  ||  lc.cmd == LC_LOAD_UPWARD_DYLIB  ||  
            lc.cmd == LC_LOAD_WEAK_DYLIB  ||  lc.cmd == LC_REEXPORT_DYLIB) 
        {
            // Look for AppleScriptObjC linkage.
            FileSlice dylibSlice;
            if (!cmds.slice(0, lc.cmdsize, dylibSlice)) return Error;
            struct dylib_command dylib;
            if (!dylibSlice.pread(&dylib, sizeof(dylib))) return Error;

            const char *asoFramework = 
                "/System/Library/Frameworks/AppleScriptObjC.framework"
                "/Versions/A/AppleScriptObjC";
            size_t asoLen = strlen(asoFramework);

            FileSlice nameSlice;
            if (dylibSlice.slice(dylib.dylib.name.offset, asoLen, nameSlice)) {
                char name[asoLen];
                if (!nameSlice.pread(name, asoLen)) return Error;
                if (0 == memcmp(name, asoFramework, asoLen)) {
                    linksToAppleScriptObjC = YES;
                }
            }
        }
        else if (lc.cmd == Arch::segment_cmd) {
            typename Arch::segment_command_t seg;
            if (!cmds.pread(&seg, sizeof(seg))) return Error;

            if (Arch::isObjCSegment(seg.segname)) {
                // ObjC segment. 
                // Look for image info section.
                // Look for class implementations and class references.
                FileSlice sections;
                if (!cmds.slice(0, seg.cmdsize, sections)) return Error;
                if (!sections.advance(sizeof(seg))) return Error;
                
                for (uint32_t segindex = 0; segindex < seg.nsects; segindex++) {
                    typename Arch::section_t sect;
                    if (!sections.pread(&sect, sizeof(sect))) return Error;
                    if (!Arch::isObjCSegment(sect.segname)) return Error;

                    if (!Arch::countClasses(file, sect, 
                                            classCount, classrefCount)) 
                    {
                        return Error;
                    }

                    if ((sect.flags & SECTION_TYPE) == S_REGULAR  &&  
                        Arch::isImageInfoSection(sect.sectname))
                    {
                        // ObjC image info section.
                        // Check its contents.
                        FileSlice section;
                        if (!file.slice(sect.offset, sect.size, section)) {
                            return Error;
                        }
                        // The subset of objc_image_info that was in use for GC.
                        struct {
                            uint32_t version;
                            uint32_t flags;
                        } ii;
                        if (!section.pread(&ii, sizeof(ii))) return Error;
                        if (ii.flags & (1<<1)) {
                            // App wants GC. 
                            // Don't return yet because we need to 
                            // check the AppleScriptObjC exception.
                            wantsGC = YES;
                        }
                    }

                    if (!sections.advance(sizeof(sect))) return Error;
                }
            }
        }

        if (!cmds.advance(lc.cmdsize)) return Error;
    }

    if (!wantsGC) {
        // No GC bit set.
        return WithoutGC;
    }
    else if (linksToAppleScriptObjC && classCount == 0 && classrefCount == 1) {
        // Has GC bit but falls under the AppleScriptObjC exception.
        return WithoutGC;
    }
    else {
        // Has GC bit and is not AppleScriptObjC.
        return WithGC;
    }
}


static int sliceRequiresGC(FileSlice file)
{
    // Read mach-o header.
    struct mach_header_64 mh;
    if (!file.pread(&mh, sizeof(mh))) return Error;

    // Check header magic. We assume only host-endian slices can support GC.
    switch (mh.magic) {
    case MH_MAGIC:
        return sliceRequiresGC<Arch32>(*(struct mach_header *)&mh, file);
    case MH_MAGIC_64:
        return sliceRequiresGC<Arch64>(mh, file);
    default:
        return WithoutGC;
    }
}


// Returns 1 if any slice requires GC.
// Returns 0 if no slice requires GC.
// Returns -1 on any I/O or file format error.
int objc_appRequiresGC(int fd)
{
    struct stat st;
    if (fstat(fd, &st) < 0) return Error;

    FileSlice file(fd, 0, st.st_size);

    // Read fat header, if any.
    struct fat_header fh;

    if (! file.pread(&fh, sizeof(fh))) return Error;

    int result;

    if (OSSwapBigToHostInt32(fh.magic) == FAT_MAGIC) {
        // Fat header.

        size_t nfat_arch = OSSwapBigToHostInt32(fh.nfat_arch);
        // Disallow abusively-large files that could hang this checker.
        if (nfat_arch > SANE_HEADER_SIZE/sizeof(struct fat_arch)) return Error;

        size_t fat_size;
        if (umul_overflow(nfat_arch, sizeof(struct fat_arch), &fat_size)) {
            return Error;
        }

        FileSlice archlist;
        if (!file.slice(sizeof(fh), fat_size, archlist)) return Error;

        result = WithoutGC;
        for (size_t i = 0; i < nfat_arch; i++) {
            struct fat_arch fa;
            if (!archlist.pread(&fa, sizeof(fa))) return Error;
            if (!archlist.advance(sizeof(fa))) return Error;

            FileSlice thin;
            if (!file.slice(OSSwapBigToHostInt32(fa.offset), 
                            OSSwapBigToHostInt32(fa.size), thin)) 
            {
                return Error;
            }
            switch (sliceRequiresGC(thin)) {
            case WithoutGC: break; // no change
            case WithGC: if (result != Error) result = WithGC; break;
            case Error: result = Error; break;
            }
        }
    }
    else {
        // Thin header or not a header.
        result = sliceRequiresGC(file);
    }
    
    return result;
}

// SUPPORT_GC_COMPAT
#endif