diff --git a/configure.in b/configure.in index 7848087cde73..f1a932178402 100644 --- a/configure.in +++ b/configure.in @@ -3309,7 +3309,7 @@ esac _SAVE_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -D_GNU_SOURCE" -AC_CHECK_FUNCS(dladdr) +AC_CHECK_FUNCS(dladdr memmem) CFLAGS="$_SAVE_CFLAGS" if test ! "$GNU_CXX"; then diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index 3f8b1ba95583..6e34ad225296 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -63,6 +63,9 @@ USING_INDEXEDDB_NAMESPACE namespace { +// This is just to give us some random marker in the byte stream +static const PRUint64 kTotallyRandomNumber = LL_INIT(0x286F258B, 0x177D47A9); + class AddHelper : public AsyncConnectionHelper { public: @@ -96,7 +99,6 @@ public: AsyncConnectionHelper::ReleaseMainThreadObjects(); } - nsresult ModifyValueForNewKey(); nsresult UpdateIndexes(mozIStorageConnection* aConnection, PRInt64 aObjectDataId); @@ -840,9 +842,8 @@ IDBObjectStore::DeserializeValue(JSContext* aCx, JSAutoStructuredCloneBuffer& aBuffer, jsval* aValue) { - /* - * This function can be called on multiple threads! Be careful! - */ + NS_ASSERTION(NS_IsMainThread(), + "Should only be deserializing on the main thread!"); NS_ASSERTION(aCx, "A JSContext is required!"); if (!aBuffer.data()) { @@ -861,9 +862,8 @@ IDBObjectStore::SerializeValue(JSContext* aCx, JSAutoStructuredCloneBuffer& aBuffer, jsval aValue) { - /* - * This function can be called on multiple threads! Be careful! - */ + NS_ASSERTION(NS_IsMainThread(), + "Should only be serializing on the main thread!"); NS_ASSERTION(aCx, "A JSContext is required!"); JSAutoRequest ar(aCx); @@ -871,9 +871,62 @@ IDBObjectStore::SerializeValue(JSContext* aCx, return aBuffer.write(aCx, aValue, nsnull); } +static inline jsdouble +SwapBytes(PRUint64 u) +{ +#ifdef IS_BIG_ENDIAN + return ((u & 0x00000000000000ffLLU) << 56) | + ((u & 0x000000000000ff00LLU) << 40) | + ((u & 0x0000000000ff0000LLU) << 24) | + ((u & 0x00000000ff000000LLU) << 8) | + ((u & 0x000000ff00000000LLU) >> 8) | + ((u & 0x0000ff0000000000LLU) >> 24) | + ((u & 0x00ff000000000000LLU) >> 40) | + ((u & 0xff00000000000000LLU) >> 56); +#else + return u; +#endif +} + +nsresult +IDBObjectStore::ModifyValueForNewKey(JSAutoStructuredCloneBuffer& aBuffer, + Key& aKey) +{ + NS_ASSERTION(IsAutoIncrement() && KeyPath().IsEmpty() && aKey.IsInt(), + "Don't call me!"); + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread"); + NS_ASSERTION(mKeyPathSerializationOffset, "How did this happen?"); + + // The minus 8 dangling off the end here is to account for the null entry + // that terminates the buffer + const PRUint32 keyPropLen = mKeyPathSerialization.nbytes() - + mKeyPathSerializationOffset - sizeof(PRUint64); + + const char* location = nsCRT::memmem((char*)aBuffer.data(), + aBuffer.nbytes(), + (char*)mKeyPathSerialization.data() + + mKeyPathSerializationOffset, + keyPropLen); + NS_ASSERTION(location, "How did this happen?"); + + // This is a duplicate of the js engine's byte munging here + union { + jsdouble d; + PRUint64 u; + } pun; + + pun.d = SwapBytes(aKey.IntValue()); + + memcpy(const_cast(location) + keyPropLen - + sizeof(pun.u), // We're overwriting the last 8 bytes + &pun.u, sizeof(PRUint64)); + return NS_OK; +} + IDBObjectStore::IDBObjectStore() : mId(LL_MININT), - mAutoIncrement(PR_FALSE) + mAutoIncrement(PR_FALSE), + mKeyPathSerializationOffset(0) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); } @@ -932,11 +985,47 @@ IDBObjectStore::GetAddInfo(JSContext* aCx, rv = GetIndexUpdateInfo(info, aCx, aValue, aUpdateInfoArray); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - if (!IDBObjectStore::SerializeValue(aCx, aCloneBuffer, aValue)) { - return NS_ERROR_DOM_DATA_CLONE_ERR; + const jschar* keyPathChars = + reinterpret_cast(mKeyPath.get()); + const size_t keyPathLen = mKeyPath.Length(); + JSBool ok = JS_FALSE; + + if (!mKeyPath.IsEmpty() && aKey.IsUnset()) { + NS_ASSERTION(mAutoIncrement, "Should have bailed earlier!"); + + jsval key; + ok = JS_NewNumberValue(aCx, kTotallyRandomNumber, &key); + NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + ok = JS_DefineUCProperty(aCx, JSVAL_TO_OBJECT(aValue), keyPathChars, + keyPathLen, key, nsnull, nsnull, + JSPROP_ENUMERATE); + NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + // From this point on we have to try to remove the property. + rv = EnsureKeyPathSerializationData(aCx); } - return NS_OK; + // We guard on rv being a success because we need to run the property + // deletion code below even if we should not be serializing the value + if (NS_SUCCEEDED(rv) && + !IDBObjectStore::SerializeValue(aCx, aCloneBuffer, aValue)) { + rv = NS_ERROR_DOM_DATA_CLONE_ERR; + } + + if (ok) { + // If this fails, we lose, and the web page sees a magical property + // appear on the object :-( + jsval succeeded; + ok = JS_DeleteUCProperty2(aCx, JSVAL_TO_OBJECT(aValue), keyPathChars, + keyPathLen, &succeeded); + NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + NS_ASSERTION(JSVAL_IS_BOOLEAN(succeeded), "Wtf?"); + NS_ENSURE_TRUE(JSVAL_TO_BOOLEAN(succeeded), + NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + } + + return rv; } nsresult @@ -987,6 +1076,46 @@ IDBObjectStore::AddOrPut(const jsval& aValue, return NS_OK; } +nsresult +IDBObjectStore::EnsureKeyPathSerializationData(JSContext* aCx) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread"); + + if (!mKeyPathSerializationOffset) { + JSBool ok; + + JSAutoStructuredCloneBuffer emptyObjectBuffer; + JSAutoStructuredCloneBuffer fakeObjectBuffer; + + const jschar* keyPathChars = + reinterpret_cast(mKeyPath.get()); + const size_t keyPathLen = mKeyPath.Length(); + + JSObject* object = JS_NewObject(aCx, nsnull, nsnull, nsnull); + NS_ENSURE_TRUE(object, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + ok = emptyObjectBuffer.write(aCx, OBJECT_TO_JSVAL(object)); + NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + jsval key; + // This is just to give us some random marker in the byte stream + ok = JS_NewNumberValue(aCx, kTotallyRandomNumber, &key); + NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + ok = JS_DefineUCProperty(aCx, object, keyPathChars, keyPathLen, + key, nsnull, nsnull, JSPROP_ENUMERATE); + NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + ok = fakeObjectBuffer.write(aCx, OBJECT_TO_JSVAL(object)); + NS_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + mKeyPathSerialization.swap(fakeObjectBuffer); + mKeyPathSerializationOffset = emptyObjectBuffer.nbytes(); + } + + return NS_OK; +} + NS_IMPL_CYCLE_COLLECTION_CLASS(IDBObjectStore) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IDBObjectStore) @@ -1721,7 +1850,7 @@ AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection) // Special case where someone put an object into an autoIncrement'ing // objectStore with no key in its keyPath set. We needed to figure out // which row id we would get above before we could set that properly. - rv = ModifyValueForNewKey(); + rv = mObjectStore->ModifyValueForNewKey(mCloneBuffer, mKey); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); scoper.Abandon(); @@ -1777,58 +1906,6 @@ AddHelper::GetSuccessResult(JSContext* aCx, return IDBObjectStore::GetJSValFromKey(mKey, aCx, aVal); } -nsresult -AddHelper::ModifyValueForNewKey() -{ - NS_ASSERTION(mObjectStore->IsAutoIncrement() && - !mObjectStore->KeyPath().IsEmpty() && - mKey.IsInt(), - "Don't call me!"); - - const nsString& keyPath = mObjectStore->KeyPath(); - - JSContext* cx = nsnull; - nsresult rv = nsContentUtils::ThreadJSContextStack()->GetSafeJSContext(&cx); - NS_ENSURE_SUCCESS(rv, rv); - - JSAutoRequest ar(cx); - - jsval clone; - if (!IDBObjectStore::DeserializeValue(cx, mCloneBuffer, &clone)) { - return NS_ERROR_DOM_DATA_CLONE_ERR; - } - - NS_ASSERTION(!JSVAL_IS_PRIMITIVE(clone), "We should have an object!"); - - JSObject* obj = JSVAL_TO_OBJECT(clone); - JSBool ok; - - const jschar* keyPathChars = reinterpret_cast(keyPath.get()); - const size_t keyPathLen = keyPath.Length(); - -#ifdef DEBUG - { - jsval prop; - ok = JS_GetUCProperty(cx, obj, keyPathChars, keyPathLen, &prop); - NS_ASSERTION(ok && JSVAL_IS_VOID(prop), "Already has a key prop!"); - } -#endif - - jsval key; - ok = JS_NewNumberValue(cx, mKey.IntValue(), &key); - NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); - - ok = JS_DefineUCProperty(cx, obj, keyPathChars, keyPathLen, key, nsnull, - nsnull, JSPROP_ENUMERATE); - NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); - - if (!IDBObjectStore::SerializeValue(cx, mCloneBuffer, OBJECT_TO_JSVAL(obj))) { - return NS_ERROR_DOM_DATA_CLONE_ERR; - } - - return NS_OK; -} - nsresult GetHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { diff --git a/dom/indexedDB/IDBObjectStore.h b/dom/indexedDB/IDBObjectStore.h index cbbaeeb0de90..50ee4c539db4 100644 --- a/dom/indexedDB/IDBObjectStore.h +++ b/dom/indexedDB/IDBObjectStore.h @@ -157,6 +157,9 @@ public: return mTransaction; } + nsresult ModifyValueForNewKey(JSAutoStructuredCloneBuffer& aBuffer, + Key& aKey); + protected: IDBObjectStore(); ~IDBObjectStore(); @@ -175,6 +178,8 @@ protected: nsIIDBRequest** _retval, bool aOverwrite); + nsresult EnsureKeyPathSerializationData(JSContext* aCx); + private: nsRefPtr mTransaction; @@ -188,6 +193,11 @@ private: PRUint32 mDatabaseId; PRUint32 mStructuredCloneVersion; + // Used to store a serialized representation of the fake property + // entry used to handle autoincrement with keypaths. + JSAutoStructuredCloneBuffer mKeyPathSerialization; + PRUint32 mKeyPathSerializationOffset; + nsTArray > mCreatedIndexes; }; diff --git a/xpcom/ds/nsCRT.cpp b/xpcom/ds/nsCRT.cpp index ca1f49997b5d..eb90c2a59a2c 100644 --- a/xpcom/ds/nsCRT.cpp +++ b/xpcom/ds/nsCRT.cpp @@ -163,6 +163,28 @@ PRInt32 nsCRT::strncmp(const PRUnichar* s1, const PRUnichar* s2, PRUint32 n) { return 0; } +const char* nsCRT::memmem(const char* haystack, PRUint32 haystackLen, + const char* needle, PRUint32 needleLen) +{ + // Sanity checking + if (!(haystack && needle && haystackLen && needleLen && + needleLen <= haystackLen)) + return NULL; + +#ifdef HAVE_MEMMEM + return (const char*)::memmem(haystack, haystackLen, needle, needleLen); +#else + // No memmem means we need to roll our own. This isn't really optimized + // for performance ... if that becomes an issue we can take some inspiration + // from the js string compare code in jsstr.cpp + for (PRInt32 i = 0; i < haystackLen - needleLen; i++) { + if (!memcmp(haystack + i, needle, needleLen)) + return haystack + i; + } +#endif + return NULL; +} + PRUnichar* nsCRT::strdup(const PRUnichar* str) { PRUint32 len = nsCRT::strlen(str); diff --git a/xpcom/ds/nsCRT.h b/xpcom/ds/nsCRT.h index f2ed31eab54a..c9790b977b1c 100644 --- a/xpcom/ds/nsCRT.h +++ b/xpcom/ds/nsCRT.h @@ -210,6 +210,12 @@ public: static PRInt32 strncmp(const PRUnichar* s1, const PRUnichar* s2, PRUint32 aMaxLen); + // The GNU libc has memmem, which is strstr except for binary data + // This is our own implementation that uses memmem on platforms + // where it's available. + static const char* memmem(const char* haystack, PRUint32 haystackLen, + const char* needle, PRUint32 needleLen); + // You must use nsCRT::free(PRUnichar*) to free memory allocated // by nsCRT::strdup(PRUnichar*). static PRUnichar* strdup(const PRUnichar* str);