17 Replies
      Latest reply on Jan 10, 2018 2:12 AM by jctjct
      Mapdiva Level 1 Level 1 (0 points)

        )I've already reported this bug and raised a DTS incident to get it looked at, but I'm also putting it out there in case anyone else runs into it, or can suggest a workaround within app code. (Bug # 35926492)

         

        10.13.2 breaks the function [-NSKeyedDearchiver decodeArrayOfObjCType:count:at:] when the obj-C type is @encode(float) ("f"). It now throws an exception as follows:     *** -[_NSKeyedCoderOldStyleArray initWithCoder:]: unable to decode element in array of size (4) and count (6).


        Note that all OS up to 10.13.1 work fine.

         

        A test project reproduces the issue with the following code:

         

        - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
        {
        
          NSString* path = [@"~/Desktop/testArchive.plist" stringByExpandingTildeInPath];
        
          float testValues[] = {1.0, 2.0, 3.5, 4.6, 3.1415926, 0.0};
        
          NSMutableData* testArchive = [NSMutableData data];
        
          NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:testArchive];
        
          [archiver encodeArrayOfObjCType:@encode(float) count:6 at:testValues];
          [archiver finishEncoding];
        
          [testArchive writeToFile:path atomically:YES];
        
             // now read it back in and see if it can be dearchived - in 10.13.2, this will throw an exception.
        
          NSData* readArchive = [NSData dataWithContentsOfFile:path];
          NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:readArchive];
        
          float readValues[6];
        
          [unarchiver decodeArrayOfObjCType:@encode(float) count:6 at:readValues]; //<--- throws
          [unarchiver finishDecoding];
        
          for( int i = 0; i < 6; ++i )
               NSAssert( readValues[i] == testValues[i], @"dearchived data does not match values archived");
        }
        
        

         

        Note that there are probably better, more robust ways to archive a list of floats. I'm not looking for suggestions about that. The issue is that there are thousands of archives out there in customer land that I need to be able to read.

         

        I've tried substituting the class _NSKeyedCoderOldStyleArray with my own to see if I can read the archive keys directly - I can get NS.count, NS.size and NS.type OK, but $0..$n, nope. Quincey Morris suggested that the actual keys may not literally be these, which is what the archive file contains. I've tried without the leading $ symbol, but with no luck. Someone may know how the keys are changed when dearchiving which might give me a possible workaround.

        • Re: Dearchiving array of floats in 10.13.2 broken
          jmarquez Level 1 Level 1 (0 points)

          Replace your "float" types with "CGFloat".

          • Re: Dearchiving array of floats in 10.13.2 broken
            Macho Man Randy Savage Level 3 Level 3 (365 points)

            Have you tried changing the decodingFailurePolicy to NSDecodingFailurePolicySetErrorAndReturn to see if you get an error with any useful information?

            • Re: Dearchiving array of floats in 10.13.2 broken
              SBWonderBunny Level 1 Level 1 (0 points)

              I am having the same problem.  Even writing with NSKeyedArchiver and reading with NSKeyedUnarchiver with same object and build on 10.13.2 is failing.  And of course existing archives are unreadable.

               

              I submitted a bug also.  Hopefully there is some solution upcoming to this issue, please let us know if they respond to your bug.  I noticed that iMovie had a number of crash reports recently with NSKeyArchiver is the crash logs.

              • Re: Dearchiving array of floats in 10.13.2 broken
                Claude31 Level 7 Level 7 (4,255 points)

                Could you replace

                 

                float testValues[] = {1.0, 2.0, 3.5, 4.6, 3.1415926, 0.0};
                

                by

                 

                testValues = (float *)malloc(sizeof(float) * 6);
                

                to be sure to have the correct size ?

                  • Re: Dearchiving array of floats in 10.13.2 broken
                    SBWonderBunny Level 1 Level 1 (0 points)

                    Hi,

                     

                    I think people are missing the point.  Existing data that used NSKeyedArchiver can no longer be decoded on 10.13.2.  Any attempts to change object decoded into just results in an archive mismatch.  This is new behavior that was introduced on the last update. 

                     

                    Has anyone tried 10.13.3 beta to see if it was fixed?

                      • Re: Dearchiving array of floats in 10.13.2 broken
                        Claude31 Level 7 Level 7 (4,255 points)

                        I run 10.13.2 and have data encoded with NSKeyedArchiver ; they are decoded so far without problem with unarchiver.decodeObject(forKey:)

                         

                        What do you mean exactly by :

                        Any attempts to change object decoded into just results in an archive mismatch.

                         

                        I do modify data and archive back then dearchive without problem.

                          • Re: Dearchiving array of floats in 10.13.2 broken
                            QuinceyMorris Level 8 Level 8 (5,690 points)

                            This is a problem with a single API ("decodeArray(ofObjCType:count:at:)" is the Swift version) and a single Obj-C type encoding string (@encode(float), aka "f"). You won't see a problem unless you use "encodeArray(ofObjCType:count:at:)" to encode the data. The equivalent code using @encode(double) for both encoding and decoding works just fine. And, no, trying to trick it by decoding with a different C type of the same size as float (e.g. @encode(uint32_t)) doesn't work either.

                             

                            Note that this is a non-keyed encode, which is legal but unusual in a keyed archive.

                      • Re: Dearchiving array of floats in 10.13.2 broken
                        itaiferber Apple Staff Apple Staff (0 points)

                        One option that you have until the fix is pushed out is to modify the archived data to allow you to decode your floats as doubles instead. Since floats are losslessly convertible to double values, decoding an array of doubles should give you the same values as you had before, just in a larger container at runtime.

                         

                        Unfortunately, _NSKeyedCoderOldStyleArray verifies that the ObjC type you gave it on encode is the same as the ObjC type you use on decode, so you can't just request to [decodeArrayOfObjCType:@encode(double) count:... at:...]. However, what you can do is modify the archive to change the ObjC type inside to double (until a fix for this is released, at least) — this is certainly a hacky fix, but might suit your needs in the meantime:

                         

                        #import <Foundation/Foundation.h>
                        
                        int main(int argc, char *argv[]) {
                            @autoreleasepool {
                                const float array[] = {1.0, 2.0, 3.5, 4.6, 3.1415926, 0.0};
                                const NSUInteger count = sizeof(array) / sizeof(array[0]);
                        
                                NSKeyedArchiver *archiver = [NSKeyedArchiver new];
                                archiver.outputFormat = NSPropertyListXMLFormat_v1_0;
                                [archiver encodeArrayOfObjCType:@encode(float) count:count at:array];
                        
                                NSError *error = nil;
                                NSDictionary *archive = [NSPropertyListSerialization propertyListWithData:archiver.encodedData options:NSPropertyListMutableContainers format:NULL error:&error];
                                if (!archive) {
                                    NSLog(@"%@", error);
                                    return EXIT_FAILURE;
                                }
                               
                                /*
                                 archive looks like this:
                        
                                    {
                                        "$archiver" = NSKeyedArchiver;
                                        "$objects" =     (
                                                          "$null",
                                                          {
                                                              "$0" = 1;
                                                              "$1" = 2;
                                                              "$2" = "3.5";
                                                              "$3" = "4.599999904632568";
                                                              "$4" = "3.141592502593994";
                                                              "$5" = 0;
                                                              "$class" = "<CFKeyedArchiverUID 0x101d20510 [0x1015ef730]>{value = 2}";
                                                              "NS.count" = 6;
                                                              "NS.size" = 4;
                                                              "NS.type" = 102; // @encode(float)
                                                          },
                                                          {
                                                              "$classes" =             (
                                                                                        "_NSKeyedCoderOldStyleArray",
                                                                                        NSObject
                                                                                        );
                                                              "$classname" = "_NSKeyedCoderOldStyleArray";
                                                          }
                                                          );
                                        "$top" =     {
                                            "$0" = "<CFKeyedArchiverUID 0x101d20df0 [0x1015ef730]>{value = 1}";
                                        };
                                        "$version" = 100000;
                                    }
                        
                                 Modify "NS.type" = 102 to "NS.type" = 100 (@encode(double))
                                This will of course depend on the location of the array in your archive; if not consistent, there are ways of looking it up.
                                 */
                                archive[@"$objects"][1][@"NS.type"] = @(*@encode(double));
                        
                                NSData *reencodedData = [NSPropertyListSerialization dataWithPropertyList:archive format:NSPropertyListBinaryFormat_v1_0 options:0 error:&error];
                                if (!reencodedData) {
                                    NSLog(@"%@", error);
                                    return EXIT_FAILURE;
                                }
                        
                                NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:reencodedData];
                                double decoded[count];
                                memset(decoded, sizeof(double), count);
                        
                                [unarchiver decodeArrayOfObjCType:@encode(double) count:count at:decoded];
                                for (NSUInteger i = 0; i < count; i += 1) {
                                    NSLog(@"%f == %f: %d", array[i], decoded[i], array[i] == decoded[i]);
                                }
                            }
                        }
                        
                        

                         

                        This requires you to know the location of the encoded array at runtime, but if it's in a consistent spot in the archive every time, this should be relatively easy.