Search code examples
objective-cexc-bad-access

weird KERN_INVALID_ADDRESS exception?


I am facing a weird OC Exception, it looks that I am sending a message to a released address, but when I

  1. try to check if it is NULL, it still crashs.
  2. try to debug or add @try @catch, it catches nothing but runs for days, no crash at all.

exception

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000010
VM Region Info: 0x10 is not in any region.  Bytes before following region: 4304617456
      REGION TYPE                      START - END             [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      UNUSED SPACE AT START
--->  
      __TEXT                        100934000-100a98000        [ 1424K] r-x/r-x SM=COW  ...x/APP

Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [18302]
Triggered by Thread:  22

code below is not strict ,just shows the logic ( code is running in one serial dispatch queue )

struct st_type {
    void *arg; 
};

static NSMutableDictionary *dictionary;
    
// init values
void init ()
{

    // there is a static dictionary to retain the object
    dictionary = [[NSMutableDictionary alloc]init];
    
    
    // an object reference saved in dictionary and a struct holds it's pointer
    NSObject *obj = [[NSObject alloc]init];
    
    struct st_type *st = malloc(sizeof(struct st_type));
    st->arg = (__bridge void *)obj;
    dictionary[@"cached"] = obj;
    
    // then the struct * passes to every where, I think it's safe because the object always in the dictionary.
    ...
}


// the only place to release the nsobject, so I think there is no chance to EXC_BAD_ACCESS, but ...
void release_object(struct st_type *st, NSObject obj){
    [dictionary removeObjectForKey:@"cached"];
    st->arg = NULL;
}

// some where to use the struct
void use_struct(struct st_type *st){
    if(st->arg == NULL){
        return;
    }
// if I add try catch, it never crashs
//    @try {   
    NSObject *obj = (__bridge NSObject*)st->arg;
    [obj description]; // crash here.
//    } @catch (NSException *exception) { print some log but it never reaches here... }
}

Could anyone help me what I can do next to fix this error?


Solution

  • If I understand correctly you want to store a reference to an Objective-C object as a void *. This is a similar use case as the old-style context or userInfo pointers that were passed as callbacks to sheets, for example.

    See ARC: __bridge versus __bridge_retained using contextInfo test case for an example. I also assume you're using ARC (please read Casting and Object Lifetime Semantics).

    Assuming the lifetime of the struct is longer than that of the Objective-C object, and that you explicitly set and release the object in the struct, you shouldn't need a dictionary for memory management.

    Once the struct and object are allocated (using malloc and [[XX alloc] init] respectively), you can transfer ownership of the object out of ARC and store it in st->arg using a (__bridge_retained void *) cast.

    To use the object, cast using (__bridge NSObject *). This will not change ownership.

    When you are ready to release the object, pass ownership back to ARC by casting using (__bridge_transfer NSObject *). Then you can set the void * pointer to NULL.

    So overall something like:

    struct st_type {
        void *arg; 
    };
    
    void init()
    {
        NSObject *obj = [[NSObject alloc] init];
        struct st_type *st = malloc(sizeof(struct st_type));
        // transfer ownership out of ARC
        st->arg = (__bridge_retained void *)obj;
    }
    
    void use_struct(struct st_type *st){
        // no change in ownership
        NSObject *obj = (__bridge NSObject *)st->arg;
        // use `obj`
    }
    
    void release_object(struct st_type *st){
        // transfer ownership back to ARC
        NSObject *obj = (__bridge_transfer NSObject *)st->arg;
        st->arg = NULL;
    }