LCOV - code coverage report
Current view: directory - dom/indexedDB - Key.cpp (source / functions) Found Hit Coverage
Test: app.info Lines: 148 134 90.5 %
Date: 2012-06-02 Functions: 6 6 100.0 %

       1                 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2                 : /* vim: set ts=2 et sw=2 tw=80: */
       3                 : /* ***** BEGIN LICENSE BLOCK *****
       4                 :  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
       5                 :  *
       6                 :  * The contents of this file are subject to the Mozilla Public License Version
       7                 :  * 1.1 (the "License"); you may not use this file except in compliance with
       8                 :  * the License. You may obtain a copy of the License at
       9                 :  * http://www.mozilla.org/MPL/
      10                 :  *
      11                 :  * Software distributed under the License is distributed on an "AS IS" basis,
      12                 :  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
      13                 :  * for the specific language governing rights and limitations under the
      14                 :  * License.
      15                 :  *
      16                 :  * The Original Code is Indexed Database.
      17                 :  *
      18                 :  * The Initial Developer of the Original Code is The Mozilla Foundation.
      19                 :  * Portions created by the Initial Developer are Copyright (C) 2010
      20                 :  * the Initial Developer. All Rights Reserved.
      21                 :  *
      22                 :  * Contributor(s):
      23                 :  *   Jan Varga <Jan.Varga@gmail.com>
      24                 :  *   Jonas Sicking <jonas@sicking.cc>
      25                 :  *
      26                 :  * Alternatively, the contents of this file may be used under the terms of
      27                 :  * either the GNU General Public License Version 2 or later (the "GPL"), or
      28                 :  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
      29                 :  * in which case the provisions of the GPL or the LGPL are applicable instead
      30                 :  * of those above. If you wish to allow use of your version of this file only
      31                 :  * under the terms of either the GPL or the LGPL, and not to allow others to
      32                 :  * use your version of this file under the terms of the MPL, indicate your
      33                 :  * decision by deleting the provisions above and replace them with the notice
      34                 :  * and other provisions required by the GPL or the LGPL. If you do not delete
      35                 :  * the provisions above, a recipient may use your version of this file under
      36                 :  * the terms of any one of the MPL, the GPL or the LGPL.
      37                 :  *
      38                 :  * ***** END LICENSE BLOCK ***** */
      39                 : 
      40                 : #include "Key.h"
      41                 : #include "nsIStreamBufferAccess.h"
      42                 : #include "jsfriendapi.h"
      43                 : #include "nsAlgorithm.h"
      44                 : #include "nsContentUtils.h"
      45                 : #include "nsJSUtils.h"
      46                 : #include "xpcpublic.h"
      47                 : 
      48                 : USING_INDEXEDDB_NAMESPACE
      49                 : 
      50                 : /*
      51                 :  Here's how we encode keys:
      52                 : 
      53                 :  Basic strategy is the following
      54                 : 
      55                 :  Numbers: 1 n n n n n n n n    ("n"s are encoded 64bit float)
      56                 :  Dates:   2 n n n n n n n n    ("n"s are encoded 64bit float)
      57                 :  Strings: 3 s s s ... 0        ("s"s are encoded unicode bytes)
      58                 :  Arrays:  4 i i i ... 0        ("i"s are encoded array items)
      59                 : 
      60                 : 
      61                 :  When encoding floats, 64bit IEEE 754 are almost sortable, except that
      62                 :  positive sort lower than negative, and negative sort descending. So we use
      63                 :  the following encoding:
      64                 :  
      65                 :  value < 0 ?
      66                 :    (-to64bitInt(value)) :
      67                 :    (to64bitInt(value) | 0x8000000000000000)
      68                 : 
      69                 : 
      70                 :  When encoding strings, we use variable-size encoding per the following table
      71                 :  
      72                 :  Chars 0         - 7E           are encoded as 0xxxxxxx with 1 added
      73                 :  Chars 7F        - (3FFF+7F)    are encoded as 10xxxxxx xxxxxxxx with 7F subtracted
      74                 :  Chars (3FFF+80) - FFFF         are encoded as 11xxxxxx xxxxxxxx xx000000
      75                 : 
      76                 :  This ensures that the first byte is never encoded as 0, which means that the
      77                 :  string terminator (per basic-stategy table) sorts before any character.
      78                 :  The reason that (3FFF+80) - FFFF is encoded "shifted up" 6 bits is to maximize
      79                 :  the chance that the last character is 0. See below for why.
      80                 : 
      81                 : 
      82                 :  When encoding Arrays, we use an additional trick. Rather than adding a byte
      83                 :  containing the value '4' to indicate type, we instead add 4 to the next byte.
      84                 :  This is usually the byte containing the type of the first item in the array.
      85                 :  So simple examples are
      86                 : 
      87                 :  ["foo"]       7 s s s 0 0                              // 7 is 3 + 4
      88                 :  [1, 2]        5 n n n n n n n n 1 n n n n n n n n 0    // 5 is 1 + 4
      89                 : 
      90                 :  Whe do this iteratively if the first item in the array is also an array
      91                 : 
      92                 :  [["foo"]]    11 s s s 0 0 0
      93                 : 
      94                 :  However, to avoid overflow in the byte, we only do this 3 times. If the first
      95                 :  item in an array is an array, and that array also has an array as first item,
      96                 :  we simply write out the total value accumulated so far and then follow the
      97                 :  "normal" rules.
      98                 : 
      99                 :  [[["foo"]]]  12 3 s s s 0 0 0 0
     100                 : 
     101                 :  There is another edge case that can happen though, which is that the array
     102                 :  doesn't have a first item to which we can add 4 to the type. Instead the
     103                 :  next byte would normally be the array terminator (per basic-strategy table)
     104                 :  so we simply add the 4 there.
     105                 : 
     106                 :  [[]]         8 0             // 8 is 4 + 4 + 0
     107                 :  []           4               // 4 is 4 + 0
     108                 :  [[], "foo"]  8 3 s s s 0 0   // 8 is 4 + 4 + 0
     109                 : 
     110                 :  Note that the max-3-times rule kicks in before we get a chance to add to the
     111                 :  array terminator
     112                 : 
     113                 :  [[[]]]       12 0 0 0        // 12 is 4 + 4 + 4
     114                 : 
     115                 :  We could use a much higher number than 3 at no complexity or performance cost,
     116                 :  however it seems unlikely that it'll make a practical difference, and the low
     117                 :  limit makes testing eaiser.
     118                 : 
     119                 : 
     120                 :  As a final optimization we do a post-encoding step which drops all 0s at the
     121                 :  end of the encoded buffer.
     122                 :  
     123                 :  "foo"         // 3 s s s
     124                 :  1             // 1 bf f0
     125                 :  ["a", "b"]    // 7 s 3 s
     126                 :  [1, 2]        // 5 bf f0 0 0 0 0 0 0 1 c0
     127                 :  [[]]          // 8
     128                 : */
     129                 : 
     130                 : const int MaxArrayCollapse = 3;
     131                 : 
     132                 : nsresult
     133           11743 : Key::EncodeJSVal(JSContext* aCx, const jsval aVal, PRUint8 aTypeOffset)
     134                 : {
     135                 :   PR_STATIC_ASSERT(eMaxType * MaxArrayCollapse < 256);
     136                 : 
     137           11743 :   if (JSVAL_IS_STRING(aVal)) {
     138            7524 :     nsDependentJSString str;
     139            3762 :     if (!str.init(aCx, aVal)) {
     140               0 :       return NS_ERROR_OUT_OF_MEMORY;
     141                 :     }
     142            3762 :     EncodeString(str, aTypeOffset);
     143            3762 :     return NS_OK;
     144                 :   }
     145                 : 
     146            7981 :   if (JSVAL_IS_INT(aVal)) {
     147            1968 :     EncodeNumber((double)JSVAL_TO_INT(aVal), eFloat + aTypeOffset);
     148            1968 :     return NS_OK;
     149                 :   }
     150                 : 
     151            6013 :   if (JSVAL_IS_DOUBLE(aVal)) {
     152             704 :     double d = JSVAL_TO_DOUBLE(aVal);
     153             704 :     if (DOUBLE_IS_NaN(d)) {
     154              12 :       return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
     155                 :     }
     156             692 :     EncodeNumber(d, eFloat + aTypeOffset);
     157             692 :     return NS_OK;
     158                 :   }
     159                 : 
     160            5309 :   if (!JSVAL_IS_PRIMITIVE(aVal)) {
     161            5280 :     JSObject* obj = JSVAL_TO_OBJECT(aVal);
     162            5280 :     if (JS_IsArrayObject(aCx, obj)) {
     163            4366 :       aTypeOffset += eMaxType;
     164                 : 
     165            4366 :       if (aTypeOffset == eMaxType * MaxArrayCollapse) {
     166             638 :         mBuffer.Append(aTypeOffset);
     167             638 :         aTypeOffset = 0;
     168                 :       }
     169            4366 :       NS_ASSERTION((aTypeOffset % eMaxType) == 0 &&
     170                 :                    aTypeOffset < (eMaxType * MaxArrayCollapse),
     171                 :                    "Wrong typeoffset");
     172                 : 
     173                 :       uint32_t length;
     174            4366 :       if (!JS_GetArrayLength(aCx, obj, &length)) {
     175               0 :         return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     176                 :       }
     177                 : 
     178            8912 :       for (uint32_t index = 0; index < length; index++) {
     179                 :         jsval val;
     180            4612 :         if (!JS_GetElement(aCx, obj, index, &val)) {
     181               0 :           return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     182                 :         }
     183                 : 
     184            4612 :         nsresult rv = EncodeJSVal(aCx, val, aTypeOffset);
     185            4612 :         NS_ENSURE_SUCCESS(rv, rv);
     186                 : 
     187            4546 :         aTypeOffset = 0;
     188                 :       }
     189                 : 
     190            4300 :       mBuffer.Append(eTerminator + aTypeOffset);
     191                 : 
     192            4300 :       return NS_OK;
     193                 :     }
     194                 : 
     195             914 :     if (JS_ObjectIsDate(aCx, obj)) {
     196             846 :       EncodeNumber(js_DateGetMsecSinceEpoch(aCx, obj), eDate + aTypeOffset);
     197             846 :       return NS_OK;
     198                 :     }
     199                 :   }
     200                 : 
     201              97 :   return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
     202                 : }
     203                 : 
     204                 : // static
     205                 : nsresult
     206            2885 : Key::DecodeJSVal(const unsigned char*& aPos, const unsigned char* aEnd,
     207                 :                  JSContext* aCx, PRUint8 aTypeOffset, jsval* aVal)
     208                 : {
     209            2885 :   if (*aPos - aTypeOffset >= eArray) {
     210             202 :     JSObject* array = JS_NewArrayObject(aCx, 0, nsnull);
     211             202 :     if (!array) {
     212               0 :       NS_WARNING("Failed to make array!");
     213               0 :       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     214                 :     }
     215                 : 
     216             202 :     aTypeOffset += eMaxType;
     217                 : 
     218             202 :     if (aTypeOffset == eMaxType * MaxArrayCollapse) {
     219              32 :       ++aPos;
     220              32 :       aTypeOffset = 0;
     221                 :     }
     222                 : 
     223             202 :     uint32_t index = 0;
     224             612 :     while (aPos < aEnd && *aPos - aTypeOffset != eTerminator) {
     225                 :       jsval val;
     226             208 :       nsresult rv = DecodeJSVal(aPos, aEnd, aCx, aTypeOffset, &val);
     227             208 :       NS_ENSURE_SUCCESS(rv, rv);
     228                 : 
     229             208 :       aTypeOffset = 0;
     230                 : 
     231             208 :       if (!JS_SetElement(aCx, array, index++, &val)) {
     232               0 :         NS_WARNING("Failed to set array element!");
     233               0 :         return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     234                 :       }
     235                 :     }
     236                 : 
     237             202 :     NS_ASSERTION(aPos >= aEnd || (*aPos % eMaxType) == eTerminator,
     238                 :                  "Should have found end-of-array marker");
     239             202 :     ++aPos;
     240                 : 
     241             202 :     *aVal = OBJECT_TO_JSVAL(array);
     242                 :   }
     243            2683 :   else if (*aPos - aTypeOffset == eString) {
     244            1160 :     nsString key;
     245             580 :     DecodeString(aPos, aEnd, key);
     246             580 :     if (!xpc::StringToJsval(aCx, key, aVal)) {
     247               0 :       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     248                 :     }
     249                 :   }
     250            2103 :   else if (*aPos - aTypeOffset == eDate) {
     251              36 :     double msec = static_cast<double>(DecodeNumber(aPos, aEnd));
     252              36 :     JSObject* date = JS_NewDateObjectMsec(aCx, msec);
     253              36 :     if (!date) {
     254               0 :       NS_WARNING("Failed to make date!");
     255               0 :       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     256                 :     }
     257                 : 
     258              36 :     *aVal = OBJECT_TO_JSVAL(date);
     259                 :   }
     260            2067 :   else if (*aPos - aTypeOffset == eFloat) {
     261            2067 :     *aVal = DOUBLE_TO_JSVAL(DecodeNumber(aPos, aEnd));
     262                 :   }
     263                 :   else {
     264               0 :     NS_NOTREACHED("Unknown key type!");
     265                 :   }
     266                 : 
     267            2885 :   return NS_OK;
     268                 : }
     269                 : 
     270                 : 
     271                 : #define ONE_BYTE_LIMIT 0x7E
     272                 : #define TWO_BYTE_LIMIT (0x3FFF+0x7F)
     273                 : 
     274                 : #define ONE_BYTE_ADJUST 1
     275                 : #define TWO_BYTE_ADJUST (-0x7F)
     276                 : #define THREE_BYTE_SHIFT 6
     277                 : 
     278                 : void
     279            3762 : Key::EncodeString(const nsAString& aString, PRUint8 aTypeOffset)
     280                 : {
     281                 :   // First measure how long the encoded string will be.
     282                 : 
     283                 :   // The +2 is for initial 3 and trailing 0. We'll compensate for multi-byte
     284                 :   // chars below.
     285            3762 :   PRUint32 size = aString.Length() + 2;
     286                 :   
     287            3762 :   const PRUnichar* start = aString.BeginReading();
     288            3762 :   const PRUnichar* end = aString.EndReading();
     289           11128 :   for (const PRUnichar* iter = start; iter < end; ++iter) {
     290            7366 :     if (*iter > ONE_BYTE_LIMIT) {
     291            1426 :       size += *iter > TWO_BYTE_LIMIT ? 2 : 1;
     292                 :     }
     293                 :   }
     294                 : 
     295                 :   // Allocate memory for the new size
     296            3762 :   PRUint32 oldLen = mBuffer.Length();
     297                 :   char* buffer;
     298            3762 :   if (!mBuffer.GetMutableData(&buffer, oldLen + size)) {
     299               0 :     return;
     300                 :   }
     301            3762 :   buffer += oldLen;
     302                 : 
     303                 :   // Write type marker
     304            3762 :   *(buffer++) = eString + aTypeOffset;
     305                 : 
     306                 :   // Encode string
     307           11128 :   for (const PRUnichar* iter = start; iter < end; ++iter) {
     308            7366 :     if (*iter <= ONE_BYTE_LIMIT) {
     309            5940 :       *(buffer++) = *iter + ONE_BYTE_ADJUST;
     310                 :     }
     311            1426 :     else if (*iter <= TWO_BYTE_LIMIT) {
     312             752 :       PRUnichar c = PRUnichar(*iter) + TWO_BYTE_ADJUST + 0x8000;
     313             752 :       *(buffer++) = (char)(c >> 8);
     314             752 :       *(buffer++) = (char)(c & 0xFF);
     315                 :     }
     316                 :     else {
     317             674 :       PRUint32 c = (PRUint32(*iter) << THREE_BYTE_SHIFT) | 0x00C00000;
     318             674 :       *(buffer++) = (char)(c >> 16);
     319             674 :       *(buffer++) = (char)(c >> 8);
     320             674 :       *(buffer++) = (char)c;
     321                 :     }
     322                 :   }
     323                 : 
     324                 :   // Write end marker
     325            3762 :   *(buffer++) = eTerminator;
     326                 :   
     327            3762 :   NS_ASSERTION(buffer == mBuffer.EndReading(), "Wrote wrong number of bytes");
     328                 : }
     329                 : 
     330                 : // static
     331                 : void
     332             580 : Key::DecodeString(const unsigned char*& aPos, const unsigned char* aEnd,
     333                 :                   nsString& aString)
     334                 : {
     335             580 :   NS_ASSERTION(*aPos % eMaxType == eString, "Don't call me!");
     336                 : 
     337             580 :   const unsigned char* buffer = aPos + 1;
     338                 : 
     339                 :   // First measure how big the decoded string will be.
     340             580 :   PRUint32 size = 0;
     341                 :   const unsigned char* iter; 
     342            4014 :   for (iter = buffer; iter < aEnd && *iter != eTerminator; ++iter) {
     343            3434 :     if (*iter & 0x80) {
     344              60 :       iter += (*iter & 0x40) ? 2 : 1;
     345                 :     }
     346            3434 :     ++size;
     347                 :   }
     348                 :   
     349                 :   // Set end so that we don't have to check for null termination in the loop
     350                 :   // below
     351             580 :   if (iter < aEnd) {
     352              14 :     aEnd = iter;
     353                 :   }
     354                 : 
     355                 :   PRUnichar* out;
     356             580 :   if (size && !aString.GetMutableData(&out, size)) {
     357               0 :     return;
     358                 :   }
     359                 : 
     360            4594 :   for (iter = buffer; iter < aEnd;) {
     361            3434 :     if (!(*iter & 0x80)) {
     362            3374 :       *out = *(iter++) - ONE_BYTE_ADJUST;
     363                 :     }
     364              60 :     else if (!(*iter & 0x40)) {
     365              32 :       PRUnichar c = (PRUnichar(*(iter++)) << 8);
     366              32 :       if (iter < aEnd) {
     367              30 :         c |= *(iter++);
     368                 :       }
     369              32 :       *out = c - TWO_BYTE_ADJUST - 0x8000;
     370                 :     }
     371                 :     else {
     372              28 :       PRUint32 c = PRUint32(*(iter++)) << (16 - THREE_BYTE_SHIFT);
     373              28 :       if (iter < aEnd) {
     374              20 :         c |= PRUint32(*(iter++)) << (8 - THREE_BYTE_SHIFT);
     375                 :       }
     376              28 :       if (iter < aEnd) {
     377              20 :         c |= *(iter++) >> THREE_BYTE_SHIFT;
     378                 :       }
     379              28 :       *out = (PRUnichar)c;
     380                 :     }
     381                 :     
     382            3434 :     ++out;
     383                 :   }
     384                 :   
     385             580 :   NS_ASSERTION(!size || out == aString.EndReading(),
     386                 :                "Should have written the whole string");
     387                 :   
     388             580 :   aPos = iter + 1;
     389                 : }
     390                 : 
     391                 : union Float64Union {
     392                 :   double d;
     393                 :   PRUint64 u;
     394                 : }; 
     395                 : 
     396                 : void
     397            4813 : Key::EncodeNumber(double aFloat, PRUint8 aType)
     398                 : {
     399                 :   // Allocate memory for the new size
     400            4813 :   PRUint32 oldLen = mBuffer.Length();
     401                 :   char* buffer;
     402            4813 :   if (!mBuffer.GetMutableData(&buffer, oldLen + 1 + sizeof(double))) {
     403               0 :     return;
     404                 :   }
     405            4813 :   buffer += oldLen;
     406                 : 
     407            4813 :   *(buffer++) = aType;
     408                 : 
     409                 :   Float64Union pun;
     410            4813 :   pun.d = aFloat;
     411                 :   PRUint64 number = pun.u & PR_UINT64(0x8000000000000000) ?
     412                 :                     -pun.u :
     413            4813 :                     (pun.u | PR_UINT64(0x8000000000000000));
     414                 : 
     415            4813 :   number = NS_SWAP64(number);
     416            4813 :   memcpy(buffer, &number, sizeof(number));
     417                 : }
     418                 : 
     419                 : // static
     420                 : double
     421            2236 : Key::DecodeNumber(const unsigned char*& aPos, const unsigned char* aEnd)
     422                 : {
     423            2236 :   NS_ASSERTION(*aPos % eMaxType == eFloat ||
     424                 :                *aPos % eMaxType == eDate, "Don't call me!");
     425                 : 
     426            2236 :   ++aPos;
     427                 : 
     428            2236 :   PRUint64 number = 0;
     429            2236 :   memcpy(&number, aPos, NS_MIN<size_t>(sizeof(number), aEnd - aPos));
     430            2236 :   number = NS_SWAP64(number);
     431                 : 
     432            2236 :   aPos += sizeof(number);
     433                 : 
     434                 :   Float64Union pun;
     435                 :   pun.u = number & PR_UINT64(0x8000000000000000) ?
     436                 :           (number & ~PR_UINT64(0x8000000000000000)) :
     437            2236 :           -number;
     438                 : 
     439            2236 :   return pun.d;
     440                 : }

Generated by: LCOV version 1.7