[PATCH] Re: Storable 2.0.0 fails on vendor perl on Mac OS X 10.1
Nicholas Clark [Sat, 25 May 2002 22:37:19 +0000 (23:37 +0100)]
Date: Sat, 25 May 2002 22:37:19 +0100
Message-ID: <20020525213719.GG299@Bagpuss.unfortu.net>

Subject: [PATCH] Storable (smaller)
From: Nicholas Clark <nick@unfortu.net>
Date: Sat, 25 May 2002 23:13:13 +0100
Message-ID: <20020525221312.GA3910@Bagpuss.unfortu.net>

p4raw-id: //depot/perl@16790

ext/Storable/ChangeLog
ext/Storable/Storable.pm
ext/Storable/Storable.xs
ext/Storable/t/downgrade.t

index a18c77f..69d790a 100644 (file)
@@ -1,3 +1,15 @@
+Sat May 25 22:38:39 BST 2002   Nicholas Clark <nick@ccl4.org>
+
+. Description:
+
+       Version 2.02
+               
+       Rewrite Storable.xs so that the file header structure for write_magic
+       is built at compile time, and check_magic attempts to the header in
+       blocks rather than byte per byte. These changes make the compiled
+       extension 2.25% smaller, but are not significant enough to give a
+       noticeable speed up.
+
 Thu May 23 22:50:41 BST 2002   Nicholas Clark <nick@ccl4.org>
 
 . Description:
index e694273..30a5304 100644 (file)
@@ -70,7 +70,7 @@ package Storable; @ISA = qw(Exporter DynaLoader);
 use AutoLoader;
 use vars qw($canonical $forgive_me $VERSION);
 
-$VERSION = '2.01';
+$VERSION = '2.02';
 *AUTOLOAD = \&AutoLoader::AUTOLOAD;            # Grrr...
 
 #
index baea2c5..712c830 100644 (file)
@@ -736,9 +736,31 @@ static stcxt_t *Context_ptr = &Context;
  * a "minor" version, to better track this kind of evolution from now on.
  * 
  */
-static char old_magicstr[] = "perl-store";     /* Magic number before 0.6 */
-static char magicstr[] = "pst0";                       /* Used as a magic number */
+static const char old_magicstr[] = "perl-store"; /* Magic number before 0.6 */
+static const char magicstr[] = "pst0";          /* Used as a magic number */
 
+#define MAGICSTR_BYTES  'p','s','t','0'
+#define OLDMAGICSTR_BYTES  'p','e','r','l','-','s','t','o','r','e'
+
+#if BYTEORDER == 0x1234
+#define BYTEORDER_BYTES  '1','2','3','4'
+#else
+#if BYTEORDER == 0x12345678
+#define BYTEORDER_BYTES  '1','2','3','4','5','6','7','8'
+#else
+#if BYTEORDER == 0x87654321
+#define BYTEORDER_BYTES  '8','7','6','5','4','3','2','1'
+#else
+#if BYTEORDER == 0x4321
+#define BYTEORDER_BYTES  '4','3','2','1'
+#else
+#error Unknown byteoder. Please append your byteorder to Storable.xs
+#endif
+#endif
+#endif
+#endif
+
+static const char byteorderstr[] = {BYTEORDER_BYTES, 0};
 
 #define STORABLE_BIN_MAJOR     2               /* Binary major "version" */
 #define STORABLE_BIN_MINOR     5               /* Binary minor "version" */
@@ -3158,52 +3180,65 @@ static int store(stcxt_t *cxt, SV *sv)
  */
 static int magic_write(stcxt_t *cxt)
 {
-       char buf[256];  /* Enough room for 256 hexa digits */
-       unsigned char c;
-       int use_network_order = cxt->netorder;
-
-       TRACEME(("magic_write on fd=%d", cxt->fio ? PerlIO_fileno(cxt->fio)
-                 : -1));
-
-       if (cxt->fio)
-               WRITE(magicstr, (SSize_t)strlen(magicstr));     /* Don't write final \0 */
-
-       /*
-        * Starting with 0.6, the "use_network_order" byte flag is also used to
-        * indicate the version number of the binary image, encoded in the upper
-        * bits. The bit 0 is always used to indicate network order.
-        */
-
-       c = (unsigned char)
-               ((use_network_order ? 0x1 : 0x0) | (STORABLE_BIN_MAJOR << 1));
-       PUTMARK(c);
-
-       /*
-        * Starting with 0.7, a full byte is dedicated to the minor version of
-        * the binary format, which is incremented only when new markers are
-        * introduced, for instance, but when backward compatibility is preserved.
-        */
-
-       PUTMARK((unsigned char) STORABLE_BIN_WRITE_MINOR);
-
-       if (use_network_order)
-               return 0;                                               /* Don't bother with byte ordering */
-
-       sprintf(buf, "%lx", (unsigned long) BYTEORDER);
-       c = (unsigned char) strlen(buf);
-       PUTMARK(c);
-       WRITE(buf, (SSize_t)c);         /* Don't write final \0 */
-       PUTMARK((unsigned char) sizeof(int));
-       PUTMARK((unsigned char) sizeof(long));
-       PUTMARK((unsigned char) sizeof(char *));
-       PUTMARK((unsigned char) sizeof(NV));
+    /*
+     * Starting with 0.6, the "use_network_order" byte flag is also used to
+     * indicate the version number of the binary image, encoded in the upper
+     * bits. The bit 0 is always used to indicate network order.
+     */
+    /*
+     * Starting with 0.7, a full byte is dedicated to the minor version of
+     * the binary format, which is incremented only when new markers are
+     * introduced, for instance, but when backward compatibility is preserved.
+     */
 
+    /* Make these at compile time.  The WRITE() macro is sufficiently complex
+       that it saves about 200 bytes doing it this way and only using it
+       once.  */
+    static const unsigned char network_file_header[] = {
+        MAGICSTR_BYTES,
+        (STORABLE_BIN_MAJOR << 1) | 1,
+        STORABLE_BIN_WRITE_MINOR
+    };
+    static const unsigned char file_header[] = {
+        MAGICSTR_BYTES,
+        (STORABLE_BIN_MAJOR << 1) | 0,
+        STORABLE_BIN_WRITE_MINOR,
+        /* sizeof the array includes the 0 byte at the end:  */
+        (char) sizeof (byteorderstr) - 1,
+        BYTEORDER_BYTES,
+        (unsigned char) sizeof(int),
+       (unsigned char) sizeof(long),
+        (unsigned char) sizeof(char *),
+       (unsigned char) sizeof(NV)
+    };
+    const unsigned char *header;
+    SSize_t length;
+
+    TRACEME(("magic_write on fd=%d", cxt->fio ? PerlIO_fileno(cxt->fio) : -1));
+
+    if (cxt->netorder) {
+        header = network_file_header;
+        length = sizeof (network_file_header);
+    } else {
+        header = file_header;
+        length = sizeof (file_header);
+    }        
+
+    if (!cxt->fio) {
+        /* sizeof the array includes the 0 byte at the end.  */
+        header += sizeof (magicstr) - 1;
+        length -= sizeof (magicstr) - 1;
+    }        
+
+    WRITE(header, length);
+
+    if (!cxt->netorder) {
        TRACEME(("ok (magic_write byteorder = 0x%lx [%d], I%d L%d P%d D%d)",
-                (unsigned long) BYTEORDER, (int) c,
+                (unsigned long) BYTEORDER, (int) sizeof (byteorderstr) - 1,
                 (int) sizeof(int), (int) sizeof(long),
                 (int) sizeof(char *), (int) sizeof(NV)));
-
-       return 0;
+    }
+    return 0;
 }
 
 /*
@@ -4916,140 +4951,159 @@ static SV *old_retrieve_hash(stcxt_t *cxt, char *cname)
  */
 static SV *magic_check(stcxt_t *cxt)
 {
-       char buf[256];
-       char byteorder[256];
-       int c;
-       int use_network_order;
-       int version_major;
-       int version_minor = 0;
+    /* The worst case for a malicious header would be old magic (which is
+       longer), major, minor, byteorder length byte of 255, 255 bytes of
+       garbage, sizeof int, long, pointer, NV.
+       So the worse of that we can read is 255 bytes of garbage plus 4.
+       Err, I am assuming 8 bit bytes here. Please file a bug report if you're
+       compiling perl on a system with chars that are larger than 8 bits.
+       (Even Crays aren't *that* perverse).
+    */
+    unsigned char buf[4 + 255];
+    unsigned char *current;
+    int c;
+    int length;
+    int use_network_order;
+    int use_NV_size;
+    int version_major;
+    int version_minor = 0;
+
+    TRACEME(("magic_check"));
 
-       TRACEME(("magic_check"));
+    /*
+     * The "magic number" is only for files, not when freezing in memory.
+     */
 
-       /*
-        * The "magic number" is only for files, not when freezing in memory.
-        */
+    if (cxt->fio) {
+        /* This includes the '\0' at the end.  I want to read the extra byte,
+           which is usually going to be the major version number.  */
+        STRLEN len = sizeof(magicstr);
+        STRLEN old_len;
 
-       if (cxt->fio) {
-               STRLEN len = sizeof(magicstr) - 1;
-               STRLEN old_len;
+        READ(buf, (SSize_t)(len));     /* Not null-terminated */
 
-               READ(buf, (SSize_t)len);                        /* Not null-terminated */
-               buf[len] = '\0';                                /* Is now */
+        /* Point at the byte after the byte we read.  */
+        current = buf + --len; /* Do the -- outside of macros.  */
 
-               if (0 == strcmp(buf, magicstr))
-                       goto magic_ok;
+        if (memNE(buf, magicstr, len)) {
+            /*
+             * Try to read more bytes to check for the old magic number, which
+             * was longer.
+             */
 
-               /*
-                * Try to read more bytes to check for the old magic number, which
-                * was longer.
-                */
+            TRACEME(("trying for old magic number"));
 
-               old_len = sizeof(old_magicstr) - 1;
-               READ(&buf[len], (SSize_t)(old_len - len));
-               buf[old_len] = '\0';                    /* Is now null-terminated */
+            old_len = sizeof(old_magicstr) - 1;
+            READ(current + 1, (SSize_t)(old_len - len));
+            
+            if (memNE(buf, old_magicstr, old_len))
+                CROAK(("File is not a perl storable"));
+            current = buf + old_len;
+        }
+        use_network_order = *current;
+    } else
+       GETMARK(use_network_order);
+        
+    /*
+     * Starting with 0.6, the "use_network_order" byte flag is also used to
+     * indicate the version number of the binary, and therefore governs the
+     * setting of sv_retrieve_vtbl. See magic_write().
+     */
 
-               if (strcmp(buf, old_magicstr))
-                       CROAK(("File is not a perl storable"));
-       }
+    version_major = use_network_order >> 1;
+    cxt->retrieve_vtbl = version_major ? sv_retrieve : sv_old_retrieve;
 
-magic_ok:
-       /*
-        * Starting with 0.6, the "use_network_order" byte flag is also used to
-        * indicate the version number of the binary, and therefore governs the
-        * setting of sv_retrieve_vtbl. See magic_write().
-        */
+    TRACEME(("magic_check: netorder = 0x%x", use_network_order));
 
-       GETMARK(use_network_order);
-       version_major = use_network_order >> 1;
-       cxt->retrieve_vtbl = version_major ? sv_retrieve : sv_old_retrieve;
 
-       TRACEME(("magic_check: netorder = 0x%x", use_network_order));
+    /*
+     * Starting with 0.7 (binary major 2), a full byte is dedicated to the
+     * minor version of the protocol.  See magic_write().
+     */
 
+    if (version_major > 1)
+        GETMARK(version_minor);
 
-       /*
-        * Starting with 0.7 (binary major 2), a full byte is dedicated to the
-        * minor version of the protocol.  See magic_write().
-        */
+    cxt->ver_major = version_major;
+    cxt->ver_minor = version_minor;
 
-       if (version_major > 1)
-               GETMARK(version_minor);
+    TRACEME(("binary image version is %d.%d", version_major, version_minor));
 
-       cxt->ver_major = version_major;
-       cxt->ver_minor = version_minor;
+    /*
+     * Inter-operability sanity check: we can't retrieve something stored
+     * using a format more recent than ours, because we have no way to
+     * know what has changed, and letting retrieval go would mean a probable
+     * failure reporting a "corrupted" storable file.
+     */
 
-       TRACEME(("binary image version is %d.%d", version_major, version_minor));
+    if (
+        version_major > STORABLE_BIN_MAJOR ||
+        (version_major == STORABLE_BIN_MAJOR &&
+         version_minor > STORABLE_BIN_MINOR)
+        ) {
+        int croak_now = 1;
+        TRACEME(("but I am version is %d.%d", STORABLE_BIN_MAJOR,
+                 STORABLE_BIN_MINOR));
+
+        if (version_major == STORABLE_BIN_MAJOR) {
+            TRACEME(("cxt->accept_future_minor is %d",
+                     cxt->accept_future_minor));
+            if (cxt->accept_future_minor < 0)
+                cxt->accept_future_minor
+                    = (SvTRUE(perl_get_sv("Storable::accept_future_minor",
+                                          TRUE))
+                       ? 1 : 0);
+            if (cxt->accept_future_minor == 1)
+                croak_now = 0;  /* Don't croak yet.  */
+        }
+        if (croak_now) {
+            CROAK(("Storable binary image v%d.%d more recent than I am (v%d.%d)",
+                   version_major, version_minor,
+                   STORABLE_BIN_MAJOR, STORABLE_BIN_MINOR));
+        }
+    }
 
-       /*
-        * Inter-operability sanity check: we can't retrieve something stored
-        * using a format more recent than ours, because we have no way to
-        * know what has changed, and letting retrieval go would mean a probable
-        * failure reporting a "corrupted" storable file.
-        */
+    /*
+     * If they stored using network order, there's no byte ordering
+     * information to check.
+     */
 
-       if (
-               version_major > STORABLE_BIN_MAJOR ||
-                       (version_major == STORABLE_BIN_MAJOR &&
-                       version_minor > STORABLE_BIN_MINOR)
-            ) {
-            int croak_now = 1;
-            TRACEME(("but I am version is %d.%d", STORABLE_BIN_MAJOR,
-                     STORABLE_BIN_MINOR));
-
-            if (version_major == STORABLE_BIN_MAJOR) {
-                TRACEME(("cxt->accept_future_minor is %d",
-                         cxt->accept_future_minor));
-                if (cxt->accept_future_minor < 0)
-                    cxt->accept_future_minor
-                        = (SvTRUE(perl_get_sv("Storable::accept_future_minor",
-                                              TRUE))
-                           ? 1 : 0);
-                if (cxt->accept_future_minor == 1)
-                    croak_now = 0;  /* Don't croak yet.  */
-            }
-            if (croak_now) {
-               CROAK(("Storable binary image v%d.%d more recent than I am (v%d.%d)",
-                       version_major, version_minor,
-                       STORABLE_BIN_MAJOR, STORABLE_BIN_MINOR));
-            }
-        }
+    if ((cxt->netorder = (use_network_order & 0x1)))   /* Extra () for -Wall */
+        return &PL_sv_undef;                   /* No byte ordering info */
 
-       /*
-        * If they stored using network order, there's no byte ordering
-        * information to check.
-        */
+    /* In C truth is 1, falsehood is 0. Very convienient.  */
+    use_NV_size = version_major >= 2 && version_minor >= 2;
 
-       if ((cxt->netorder = (use_network_order & 0x1)))        /* Extra () for -Wall */
-               return &PL_sv_undef;                    /* No byte ordering info */
+    GETMARK(c);
+    length = c + 3 + use_NV_size;
+    READ(buf, length); /* Not null-terminated */
 
-       sprintf(byteorder, "%lx", (unsigned long) BYTEORDER);
-       GETMARK(c);
-       READ(buf, c);                                           /* Not null-terminated */
-       buf[c] = '\0';                                          /* Is now */
+    TRACEME(("byte order '%.*s' %d", c, buf, c));
 
-       TRACEME(("byte order '%s'", buf));
+    if ((c != (sizeof (byteorderstr) - 1)) || memNE(buf, byteorderstr, c))
+        CROAK(("Byte order is not compatible"));
 
-       if (strcmp(buf, byteorder))
-               CROAK(("Byte order is not compatible"));
-       
-       GETMARK(c);             /* sizeof(int) */
-       if ((int) c != sizeof(int))
-               CROAK(("Integer size is not compatible"));
-
-       GETMARK(c);             /* sizeof(long) */
-       if ((int) c != sizeof(long))
-               CROAK(("Long integer size is not compatible"));
-
-       GETMARK(c);             /* sizeof(char *) */
-       if ((int) c != sizeof(char *))
-               CROAK(("Pointer integer size is not compatible"));
-
-       if (version_major >= 2 && version_minor >= 2) {
-               GETMARK(c);             /* sizeof(NV) */
-               if ((int) c != sizeof(NV))
-                       CROAK(("Double size is not compatible"));
-       }
+    current = buf + c;
+    
+    /* sizeof(int) */
+    if ((int) *current++ != sizeof(int))
+        CROAK(("Integer size is not compatible"));
+
+    /* sizeof(long) */
+    if ((int) *current++ != sizeof(long))
+        CROAK(("Long integer size is not compatible"));
+
+    /* sizeof(char *) */
+    if ((int) *current != sizeof(char *))
+        CROAK(("Pointer integer size is not compatible"));
+
+    if (use_NV_size) {
+        /* sizeof(NV) */
+        if ((int) *++current != sizeof(NV))
+            CROAK(("Double size is not compatible"));
+    }
 
-       return &PL_sv_undef;    /* OK */
+    return &PL_sv_undef;       /* OK */
 }
 
 /*
index bdda364..f308133 100644 (file)
@@ -92,11 +92,21 @@ sub thaw_hash {
 }
 
 sub thaw_scalar {
-  my ($name, $expected) = @_;
+  my ($name, $expected, $bug) = @_;
   my $scalar = eval {thaw $tests{$name}};
   is ($@, '', "Thawed $name without error?");
   isa_ok ($scalar, 'SCALAR', "Thawed $name?");
-  is ($$scalar, $expected, "And it is the data we expected?");
+  if ($bug and $] == 5.006) {
+    # Aargh. <expletive> <expletive> 5.6.0's harness doesn't even honour
+    # TODO tests.
+    warn "# Test skipped because eq is buggy for certain Unicode cases in 5.6.0";
+    warn "# Please upgrade to 5.6.1\n";
+    ok ("I'd really like to fail this test on 5.6.0 but I'm told that CPAN auto-dependancies mess up, and certain vendors only ship 5.6.0. Get your vendor to ugrade. Else upgrade your vendor.");
+    # One such vendor being the folks who brought you LONG_MIN as a positive
+    # integer.
+  } else {
+    is ($$scalar, $expected, "And it is the data we expected?");
+  }
   $scalar;
 }
 
@@ -186,9 +196,8 @@ if (eval "use Hash::Util; 1") {
 
 if ($] >= 5.006) {
   print "# We have utf8 scalars, so test that the utf8 scalars in <DATA> are valid\n";
-  print "# These seem to fail on 5.6 - you should seriously consider upgrading to 5.6.1\n" if $] == 5.006;
-  thaw_scalar ('Short 8 bit utf8 data', "\xDF");
-  thaw_scalar ('Long 8 bit utf8 data', "\xDF" x 256);
+  thaw_scalar ('Short 8 bit utf8 data', "\xDF", 1);
+  thaw_scalar ('Long 8 bit utf8 data', "\xDF" x 256, 1);
   thaw_scalar ('Short 24 bit utf8 data', chr 0xC0FFEE);
   thaw_scalar ('Long 24 bit utf8 data', chr (0xC0FFEE) x 256);
 } else {