summaryrefslogtreecommitdiffstats
path: root/storage/mozStorageAsyncStatement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'storage/mozStorageAsyncStatement.cpp')
-rw-r--r--storage/mozStorageAsyncStatement.cpp383
1 files changed, 383 insertions, 0 deletions
diff --git a/storage/mozStorageAsyncStatement.cpp b/storage/mozStorageAsyncStatement.cpp
new file mode 100644
index 000000000..d0a3eec04
--- /dev/null
+++ b/storage/mozStorageAsyncStatement.cpp
@@ -0,0 +1,383 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <limits.h>
+#include <stdio.h>
+
+#include "nsError.h"
+#include "nsMemory.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+#include "nsIClassInfoImpl.h"
+#include "Variant.h"
+
+#include "mozIStorageError.h"
+
+#include "mozStorageBindingParams.h"
+#include "mozStorageConnection.h"
+#include "mozStorageAsyncStatementJSHelper.h"
+#include "mozStorageAsyncStatementParams.h"
+#include "mozStoragePrivateHelpers.h"
+#include "mozStorageStatementRow.h"
+#include "mozStorageStatement.h"
+#include "nsDOMClassInfo.h"
+
+#include "mozilla/Logging.h"
+
+extern mozilla::LazyLogModule gStorageLog;
+
+namespace mozilla {
+namespace storage {
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsIClassInfo
+
+NS_IMPL_CI_INTERFACE_GETTER(AsyncStatement,
+ mozIStorageAsyncStatement,
+ mozIStorageBaseStatement,
+ mozIStorageBindingParams,
+ mozilla::storage::StorageBaseStatementInternal)
+
+class AsyncStatementClassInfo : public nsIClassInfo
+{
+public:
+ constexpr AsyncStatementClassInfo() {}
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD
+ GetInterfaces(uint32_t *_count, nsIID ***_array) override
+ {
+ return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement)(_count, _array);
+ }
+
+ NS_IMETHOD
+ GetScriptableHelper(nsIXPCScriptable **_helper) override
+ {
+ static AsyncStatementJSHelper sJSHelper;
+ *_helper = &sJSHelper;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetContractID(char **_contractID) override
+ {
+ *_contractID = nullptr;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetClassDescription(char **_desc) override
+ {
+ *_desc = nullptr;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetClassID(nsCID **_id) override
+ {
+ *_id = nullptr;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetFlags(uint32_t *_flags) override
+ {
+ *_flags = 0;
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ GetClassIDNoAlloc(nsCID *_cid) override
+ {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+};
+
+NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::AddRef() { return 2; }
+NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::Release() { return 1; }
+NS_IMPL_QUERY_INTERFACE(AsyncStatementClassInfo, nsIClassInfo)
+
+static AsyncStatementClassInfo sAsyncStatementClassInfo;
+
+////////////////////////////////////////////////////////////////////////////////
+//// AsyncStatement
+
+AsyncStatement::AsyncStatement()
+: StorageBaseStatementInternal()
+, mFinalized(false)
+{
+}
+
+nsresult
+AsyncStatement::initialize(Connection *aDBConnection,
+ sqlite3 *aNativeConnection,
+ const nsACString &aSQLStatement)
+{
+ MOZ_ASSERT(aDBConnection, "No database connection given!");
+ MOZ_ASSERT(!aDBConnection->isClosed(), "Database connection should be valid");
+ MOZ_ASSERT(aNativeConnection, "No native connection given!");
+
+ mDBConnection = aDBConnection;
+ mNativeConnection = aNativeConnection;
+ mSQLString = aSQLStatement;
+
+ MOZ_LOG(gStorageLog, LogLevel::Debug, ("Inited async statement '%s' (0x%p)",
+ mSQLString.get()));
+
+#ifdef DEBUG
+ // We want to try and test for LIKE and that consumers are using
+ // escapeStringForLIKE instead of just trusting user input. The idea to
+ // check to see if they are binding a parameter after like instead of just
+ // using a string. We only do this in debug builds because it's expensive!
+ const nsCaseInsensitiveCStringComparator c;
+ nsACString::const_iterator start, end, e;
+ aSQLStatement.BeginReading(start);
+ aSQLStatement.EndReading(end);
+ e = end;
+ while (::FindInReadable(NS_LITERAL_CSTRING(" LIKE"), start, e, c)) {
+ // We have a LIKE in here, so we perform our tests
+ // FindInReadable moves the iterator, so we have to get a new one for
+ // each test we perform.
+ nsACString::const_iterator s1, s2, s3;
+ s1 = s2 = s3 = start;
+
+ if (!(::FindInReadable(NS_LITERAL_CSTRING(" LIKE ?"), s1, end, c) ||
+ ::FindInReadable(NS_LITERAL_CSTRING(" LIKE :"), s2, end, c) ||
+ ::FindInReadable(NS_LITERAL_CSTRING(" LIKE @"), s3, end, c))) {
+ // At this point, we didn't find a LIKE statement followed by ?, :,
+ // or @, all of which are valid characters for binding a parameter.
+ // We will warn the consumer that they may not be safely using LIKE.
+ NS_WARNING("Unsafe use of LIKE detected! Please ensure that you "
+ "are using mozIStorageAsyncStatement::escapeStringForLIKE "
+ "and that you are binding that result to the statement "
+ "to prevent SQL injection attacks.");
+ }
+
+ // resetting start and e
+ start = e;
+ e = end;
+ }
+#endif
+
+ return NS_OK;
+}
+
+mozIStorageBindingParams *
+AsyncStatement::getParams()
+{
+ nsresult rv;
+
+ // If we do not have an array object yet, make it.
+ if (!mParamsArray) {
+ nsCOMPtr<mozIStorageBindingParamsArray> array;
+ rv = NewBindingParamsArray(getter_AddRefs(array));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ mParamsArray = static_cast<BindingParamsArray *>(array.get());
+ }
+
+ // If there isn't already any rows added, we'll have to add one to use.
+ if (mParamsArray->length() == 0) {
+ RefPtr<AsyncBindingParams> params(new AsyncBindingParams(mParamsArray));
+ NS_ENSURE_TRUE(params, nullptr);
+
+ rv = mParamsArray->AddParams(params);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ // We have to unlock our params because AddParams locks them. This is safe
+ // because no reference to the params object was, or ever will be given out.
+ params->unlock(nullptr);
+
+ // We also want to lock our array at this point - we don't want anything to
+ // be added to it.
+ mParamsArray->lock();
+ }
+
+ return *mParamsArray->begin();
+}
+
+/**
+ * If we are here then we know there are no pending async executions relying on
+ * us (StatementData holds a reference to us; this also goes for our own
+ * AsyncStatementFinalizer which proxies its release to the calling thread) and
+ * so it is always safe to destroy our sqlite3_stmt if one exists. We can be
+ * destroyed on the caller thread by garbage-collection/reference counting or on
+ * the async thread by the last execution of a statement that already lost its
+ * main-thread refs.
+ */
+AsyncStatement::~AsyncStatement()
+{
+ destructorAsyncFinalize();
+
+ // If we are getting destroyed on the wrong thread, proxy the connection
+ // release to the right thread. I'm not sure why we do this.
+ bool onCallingThread = false;
+ (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onCallingThread);
+ if (!onCallingThread) {
+ // NS_ProxyRelase only magic forgets for us if mDBConnection is an
+ // nsCOMPtr. Which it is not; it's an nsRefPtr.
+ nsCOMPtr<nsIThread> targetThread(mDBConnection->threadOpenedOn);
+ NS_ProxyRelease(targetThread, mDBConnection.forget());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// nsISupports
+
+NS_IMPL_ADDREF(AsyncStatement)
+NS_IMPL_RELEASE(AsyncStatement)
+
+NS_INTERFACE_MAP_BEGIN(AsyncStatement)
+ NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement)
+ NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement)
+ NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams)
+ NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal)
+ if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
+ foundInterface = static_cast<nsIClassInfo *>(&sAsyncStatementClassInfo);
+ }
+ else
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageAsyncStatement)
+NS_INTERFACE_MAP_END
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// StorageBaseStatementInternal
+
+Connection *
+AsyncStatement::getOwner()
+{
+ return mDBConnection;
+}
+
+int
+AsyncStatement::getAsyncStatement(sqlite3_stmt **_stmt)
+{
+#ifdef DEBUG
+ // Make sure we are never called on the connection's owning thread.
+ bool onOpenedThread = false;
+ (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onOpenedThread);
+ NS_ASSERTION(!onOpenedThread,
+ "We should only be called on the async thread!");
+#endif
+
+ if (!mAsyncStatement) {
+ int rc = mDBConnection->prepareStatement(mNativeConnection, mSQLString,
+ &mAsyncStatement);
+ if (rc != SQLITE_OK) {
+ MOZ_LOG(gStorageLog, LogLevel::Error,
+ ("Sqlite statement prepare error: %d '%s'", rc,
+ ::sqlite3_errmsg(mNativeConnection)));
+ MOZ_LOG(gStorageLog, LogLevel::Error,
+ ("Statement was: '%s'", mSQLString.get()));
+ *_stmt = nullptr;
+ return rc;
+ }
+ MOZ_LOG(gStorageLog, LogLevel::Debug, ("Initialized statement '%s' (0x%p)",
+ mSQLString.get(),
+ mAsyncStatement));
+ }
+
+ *_stmt = mAsyncStatement;
+ return SQLITE_OK;
+}
+
+nsresult
+AsyncStatement::getAsynchronousStatementData(StatementData &_data)
+{
+ if (mFinalized)
+ return NS_ERROR_UNEXPECTED;
+
+ // Pass null for the sqlite3_stmt; it will be requested on demand from the
+ // async thread.
+ _data = StatementData(nullptr, bindingParamsArray(), this);
+
+ return NS_OK;
+}
+
+already_AddRefed<mozIStorageBindingParams>
+AsyncStatement::newBindingParams(mozIStorageBindingParamsArray *aOwner)
+{
+ if (mFinalized)
+ return nullptr;
+
+ nsCOMPtr<mozIStorageBindingParams> params(new AsyncBindingParams(aOwner));
+ return params.forget();
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageAsyncStatement
+
+// (nothing is specific to mozIStorageAsyncStatement)
+
+////////////////////////////////////////////////////////////////////////////////
+//// StorageBaseStatementInternal
+
+// proxy to StorageBaseStatementInternal using its define helper.
+MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(
+ AsyncStatement,
+ if (mFinalized) return NS_ERROR_UNEXPECTED;)
+
+NS_IMETHODIMP
+AsyncStatement::Finalize()
+{
+ if (mFinalized)
+ return NS_OK;
+
+ mFinalized = true;
+
+ MOZ_LOG(gStorageLog, LogLevel::Debug, ("Finalizing statement '%s'",
+ mSQLString.get()));
+
+ asyncFinalize();
+
+ // Release the params holder, so it can release the reference to us.
+ mStatementParamsHolder = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncStatement::BindParameters(mozIStorageBindingParamsArray *aParameters)
+{
+ if (mFinalized)
+ return NS_ERROR_UNEXPECTED;
+
+ BindingParamsArray *array = static_cast<BindingParamsArray *>(aParameters);
+ if (array->getOwner() != this)
+ return NS_ERROR_UNEXPECTED;
+
+ if (array->length() == 0)
+ return NS_ERROR_UNEXPECTED;
+
+ mParamsArray = array;
+ mParamsArray->lock();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncStatement::GetState(int32_t *_state)
+{
+ if (mFinalized)
+ *_state = MOZ_STORAGE_STATEMENT_INVALID;
+ else
+ *_state = MOZ_STORAGE_STATEMENT_READY;
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// mozIStorageBindingParams
+
+BOILERPLATE_BIND_PROXIES(
+ AsyncStatement,
+ if (mFinalized) return NS_ERROR_UNEXPECTED;
+)
+
+} // namespace storage
+} // namespace mozilla