Search code examples
objective-cnspredicatenskeyedarchiver

NSPredicate error after decoding from NSData: "This predicate has evaluation disabled"


I'm trying to save an NSPredicate to the user defaults and I thought I should encode is like so, since this class conforms to NSSecureCoding:

NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"name == 'test'"];
NSData  *filterPredicateData = [NSKeyedArchiver archivedDataWithRootObject:filterPredicate
                                requiringSecureCoding:YES
                                                error:nil];

I then decode it.

NSPredicate *decodedPredicate = [NSKeyedUnarchiver unarchivedObjectOfClass:NSPredicate.class fromData:filterPredicateData error:nil];
if([filterPredicate isEqualTo:decodedPredicate]) { // YES
    someNSArrayController.filterPredicate = filterPredicate; // this works
    someNSArrayController.filterPredicate = decodedPredicate; // this throws an exception
    [someNSArray filteredArrayUsingPredicate: decodedPredicate]; // this throws an exception
}

I'm getting this exception:

This predicate has evaluation disabled

Notes:

  • decodedPredicate works as the predicate of an NSFetchRequest without issue.

    decodedPredicate and filterPredicate return the same predicateFormat.

Can anyone explain this error?


Solution

  • You need to do [decodedPredicate allowEvaluation]; first before using it.

    From the doc of allowEvaluation:

    Forces a securely decoded predicate to allow evaluation.

    Discussion
    When securely decoding NSPredicate objects that are encoded using NSSecureCoding, evaluation is disabled because it is potentially unsafe to evaluate predicates you get out of an archive.
    Before you enable evaluation, you should validate key paths, selectors, and other details to ensure no erroneous or malicious code will be executed. Once you’ve verified the predicate, you can enable the receiver for evaluation by calling allowEvaluation.

    Sample tests (if someone wants to verify it):

    //Test Sample array
    NSArray *array = @[@{@"name":@"test"}, @{@"name": @"other"}];
    
    NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"name == 'test'"];
    NSArray *filtered1 = [array filteredArrayUsingPredicate:filterPredicate];
    NSLog(@"Filtered: %@", filtered1);
    
    NSError *encodingError = nil;
    NSData  *filterPredicateData = [NSKeyedArchiver archivedDataWithRootObject:filterPredicate
                                                         requiringSecureCoding:YES
                                                                         error:&encodingError];
    if (encodingError) {
        NSLog(@"Encoding error: %@", encodingError);
    }
    
    NSError *decodingError = nil;
    NSPredicate *decodedPredicate = [NSKeyedUnarchiver unarchivedObjectOfClass:NSPredicate.class
                                                                      fromData:filterPredicateData
                                                                         error:&decodingError];
    
    if (decodingError) {
        NSLog(@"Decoding error: %@", decodingError);
    }
    NSLog(@"Initial: %@", filterPredicate);     //Let's check the `description` if there it's seems correct
    NSLog(@"Decoded: %@", decodedPredicate);    //Let's check the `description` if there it's seems correct
    if ([filterPredicate isEqualTo:decodedPredicate]) { // YES
        [decodedPredicate allowEvaluation]; //Comment this line and it will fail
        NSArray *filtered2 = [array filteredArrayUsingPredicate:decodedPredicate];
        NSLog(@"Filtered with decoded predicate: %@", filtered2);
    }