libs-base icon indicating copy to clipboard operation
libs-base copied to clipboard

NSPredicate fails to match constant values in IN clause

Open lcampbel opened this issue 3 years ago • 5 comments

The following snippet:

    NSPredicate *p = [NSPredicate predicateWithFormat:@"'a' in {'a'}"];
    BOOL b = [p evaluateWithObject:self];
    NSLog(@"b = %d", b);

logs "b = 1" on Mac OS X but "b = 0" on gnustep.

lcampbel avatar Jan 29 '22 15:01 lcampbel

The following patch fixes this bug (and doesn't cause any of the existing NSPredicate tests to fail), but I suspect it may just be a bandaid for a specific case of a more general problem.

--- NSPredicate.m.orig	2022-01-29 14:18:23.249998976 +0000
+++ NSPredicate.m	2022-01-29 15:34:39.745337585 +0000
@@ -1049,6 +1049,8 @@
 	    e = [rightResult objectEnumerator];
 	    while ((value = [e nextObject]))
 	      {
+		if ([value isKindOfClass:[GSConstantValueExpression class]])
+		  value = [value constantValue];
 		if ([value isEqual: leftResult]) 
 		  return YES;		
 	      }

lcampbel avatar Jan 29 '22 15:01 lcampbel

I've never used NSPredicate, but looking through the code it seems to me the issue might be with the GSConstantValueExpression class used to represent the array. I wonder if its -expressionValueWithObject:context: method should, in the case of an array, return a new array formed by calling that method on each of its elements. What do you think?

rfm avatar Jan 30 '22 08:01 rfm

Yes, the following patch does seem more general, fixes my use case, and doesn't break the existing NSPredicate tests:

--- NSPredicate.m.orig	2022-01-30 10:27:07.177865334 -0500
+++ NSPredicate.m	2022-01-30 10:36:17.211753217 -0500
@@ -1337,7 +1337,15 @@
 - (id) expressionValueWithObject: (id)object
 			 context: (NSMutableDictionary *)context
 {
+  if ([_obj isKindOfClass:[NSArray class]]) {
+    NSMutableArray *tmp = [NSMutableArray arrayWithCapacity:[_obj count]];
+    for (id o in _obj) {
+      [tmp addObject:[o expressionValueWithObject:o context:context]];
+    }
+    return tmp;
+  } else {
   return _obj;
+  }
 }

lcampbel avatar Jan 30 '22 15:01 lcampbel

I wrote/added a portable (not dependent on objc-2) version of that solution.

rfm avatar Jan 30 '22 17:01 rfm

This fix doesn’t work when the array is given as an argument instead of inside the format string like this:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self IN %@", @[@"yes"]];
NSArray *filteredElements = [@[@"yes", @"no"] filteredArrayUsingPredicate:predicate];
NSLog(@"Elements: %@", filteredElements);

Running the above crashes in line 1351 here, because e will be an NSString ("yes") instead of an NSExpression: https://github.com/gnustep/libs-base/blob/f3344628e5735c2782da2acd3aa3f2fced670177/Source/NSPredicate.m#L1348-L1354

Does anyone know how to fix this? I took a brief look but having a little trouble following the NSPredicate implementation.

triplef avatar Feb 28 '22 12:02 triplef

Does anyone know how to fix this? I took a brief look but having a little trouble following the NSPredicate implementation.

Fixed in https://github.com/gnustep/libs-base/pull/273

hmelder avatar Aug 24 '22 15:08 hmelder