1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/storage/src/mozStorageAsyncStatement.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,416 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include <limits.h> 1.11 +#include <stdio.h> 1.12 + 1.13 +#include "nsError.h" 1.14 +#include "nsMemory.h" 1.15 +#include "nsProxyRelease.h" 1.16 +#include "nsThreadUtils.h" 1.17 +#include "nsIClassInfoImpl.h" 1.18 +#include "nsIProgrammingLanguage.h" 1.19 +#include "Variant.h" 1.20 + 1.21 +#include "mozIStorageError.h" 1.22 + 1.23 +#include "mozStorageBindingParams.h" 1.24 +#include "mozStorageConnection.h" 1.25 +#include "mozStorageAsyncStatementJSHelper.h" 1.26 +#include "mozStorageAsyncStatementParams.h" 1.27 +#include "mozStoragePrivateHelpers.h" 1.28 +#include "mozStorageStatementRow.h" 1.29 +#include "mozStorageStatement.h" 1.30 +#include "nsDOMClassInfo.h" 1.31 + 1.32 +#include "prlog.h" 1.33 + 1.34 +#ifdef PR_LOGGING 1.35 +extern PRLogModuleInfo *gStorageLog; 1.36 +#endif 1.37 + 1.38 +namespace mozilla { 1.39 +namespace storage { 1.40 + 1.41 +//////////////////////////////////////////////////////////////////////////////// 1.42 +//// nsIClassInfo 1.43 + 1.44 +NS_IMPL_CI_INTERFACE_GETTER(AsyncStatement, 1.45 + mozIStorageAsyncStatement, 1.46 + mozIStorageBaseStatement, 1.47 + mozIStorageBindingParams, 1.48 + mozilla::storage::StorageBaseStatementInternal) 1.49 + 1.50 +class AsyncStatementClassInfo : public nsIClassInfo 1.51 +{ 1.52 +public: 1.53 + MOZ_CONSTEXPR AsyncStatementClassInfo() {} 1.54 + 1.55 + NS_DECL_ISUPPORTS_INHERITED 1.56 + 1.57 + NS_IMETHODIMP 1.58 + GetInterfaces(uint32_t *_count, nsIID ***_array) 1.59 + { 1.60 + return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement)(_count, _array); 1.61 + } 1.62 + 1.63 + NS_IMETHODIMP 1.64 + GetHelperForLanguage(uint32_t aLanguage, nsISupports **_helper) 1.65 + { 1.66 + if (aLanguage == nsIProgrammingLanguage::JAVASCRIPT) { 1.67 + static AsyncStatementJSHelper sJSHelper; 1.68 + *_helper = &sJSHelper; 1.69 + return NS_OK; 1.70 + } 1.71 + 1.72 + *_helper = nullptr; 1.73 + return NS_OK; 1.74 + } 1.75 + 1.76 + NS_IMETHODIMP 1.77 + GetContractID(char **_contractID) 1.78 + { 1.79 + *_contractID = nullptr; 1.80 + return NS_OK; 1.81 + } 1.82 + 1.83 + NS_IMETHODIMP 1.84 + GetClassDescription(char **_desc) 1.85 + { 1.86 + *_desc = nullptr; 1.87 + return NS_OK; 1.88 + } 1.89 + 1.90 + NS_IMETHODIMP 1.91 + GetClassID(nsCID **_id) 1.92 + { 1.93 + *_id = nullptr; 1.94 + return NS_OK; 1.95 + } 1.96 + 1.97 + NS_IMETHODIMP 1.98 + GetImplementationLanguage(uint32_t *_language) 1.99 + { 1.100 + *_language = nsIProgrammingLanguage::CPLUSPLUS; 1.101 + return NS_OK; 1.102 + } 1.103 + 1.104 + NS_IMETHODIMP 1.105 + GetFlags(uint32_t *_flags) 1.106 + { 1.107 + *_flags = 0; 1.108 + return NS_OK; 1.109 + } 1.110 + 1.111 + NS_IMETHODIMP 1.112 + GetClassIDNoAlloc(nsCID *_cid) 1.113 + { 1.114 + return NS_ERROR_NOT_AVAILABLE; 1.115 + } 1.116 +}; 1.117 + 1.118 +NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::AddRef() { return 2; } 1.119 +NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::Release() { return 1; } 1.120 +NS_IMPL_QUERY_INTERFACE(AsyncStatementClassInfo, nsIClassInfo) 1.121 + 1.122 +static AsyncStatementClassInfo sAsyncStatementClassInfo; 1.123 + 1.124 +//////////////////////////////////////////////////////////////////////////////// 1.125 +//// AsyncStatement 1.126 + 1.127 +AsyncStatement::AsyncStatement() 1.128 +: StorageBaseStatementInternal() 1.129 +, mFinalized(false) 1.130 +{ 1.131 +} 1.132 + 1.133 +nsresult 1.134 +AsyncStatement::initialize(Connection *aDBConnection, 1.135 + sqlite3 *aNativeConnection, 1.136 + const nsACString &aSQLStatement) 1.137 +{ 1.138 + MOZ_ASSERT(aDBConnection, "No database connection given!"); 1.139 + MOZ_ASSERT(!aDBConnection->isClosed(), "Database connection should be valid"); 1.140 + MOZ_ASSERT(aNativeConnection, "No native connection given!"); 1.141 + 1.142 + mDBConnection = aDBConnection; 1.143 + mNativeConnection = aNativeConnection; 1.144 + mSQLString = aSQLStatement; 1.145 + 1.146 + PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Inited async statement '%s' (0x%p)", 1.147 + mSQLString.get())); 1.148 + 1.149 +#ifdef DEBUG 1.150 + // We want to try and test for LIKE and that consumers are using 1.151 + // escapeStringForLIKE instead of just trusting user input. The idea to 1.152 + // check to see if they are binding a parameter after like instead of just 1.153 + // using a string. We only do this in debug builds because it's expensive! 1.154 + const nsCaseInsensitiveCStringComparator c; 1.155 + nsACString::const_iterator start, end, e; 1.156 + aSQLStatement.BeginReading(start); 1.157 + aSQLStatement.EndReading(end); 1.158 + e = end; 1.159 + while (::FindInReadable(NS_LITERAL_CSTRING(" LIKE"), start, e, c)) { 1.160 + // We have a LIKE in here, so we perform our tests 1.161 + // FindInReadable moves the iterator, so we have to get a new one for 1.162 + // each test we perform. 1.163 + nsACString::const_iterator s1, s2, s3; 1.164 + s1 = s2 = s3 = start; 1.165 + 1.166 + if (!(::FindInReadable(NS_LITERAL_CSTRING(" LIKE ?"), s1, end, c) || 1.167 + ::FindInReadable(NS_LITERAL_CSTRING(" LIKE :"), s2, end, c) || 1.168 + ::FindInReadable(NS_LITERAL_CSTRING(" LIKE @"), s3, end, c))) { 1.169 + // At this point, we didn't find a LIKE statement followed by ?, :, 1.170 + // or @, all of which are valid characters for binding a parameter. 1.171 + // We will warn the consumer that they may not be safely using LIKE. 1.172 + NS_WARNING("Unsafe use of LIKE detected! Please ensure that you " 1.173 + "are using mozIStorageAsyncStatement::escapeStringForLIKE " 1.174 + "and that you are binding that result to the statement " 1.175 + "to prevent SQL injection attacks."); 1.176 + } 1.177 + 1.178 + // resetting start and e 1.179 + start = e; 1.180 + e = end; 1.181 + } 1.182 +#endif 1.183 + 1.184 + return NS_OK; 1.185 +} 1.186 + 1.187 +mozIStorageBindingParams * 1.188 +AsyncStatement::getParams() 1.189 +{ 1.190 + nsresult rv; 1.191 + 1.192 + // If we do not have an array object yet, make it. 1.193 + if (!mParamsArray) { 1.194 + nsCOMPtr<mozIStorageBindingParamsArray> array; 1.195 + rv = NewBindingParamsArray(getter_AddRefs(array)); 1.196 + NS_ENSURE_SUCCESS(rv, nullptr); 1.197 + 1.198 + mParamsArray = static_cast<BindingParamsArray *>(array.get()); 1.199 + } 1.200 + 1.201 + // If there isn't already any rows added, we'll have to add one to use. 1.202 + if (mParamsArray->length() == 0) { 1.203 + nsRefPtr<AsyncBindingParams> params(new AsyncBindingParams(mParamsArray)); 1.204 + NS_ENSURE_TRUE(params, nullptr); 1.205 + 1.206 + rv = mParamsArray->AddParams(params); 1.207 + NS_ENSURE_SUCCESS(rv, nullptr); 1.208 + 1.209 + // We have to unlock our params because AddParams locks them. This is safe 1.210 + // because no reference to the params object was, or ever will be given out. 1.211 + params->unlock(nullptr); 1.212 + 1.213 + // We also want to lock our array at this point - we don't want anything to 1.214 + // be added to it. 1.215 + mParamsArray->lock(); 1.216 + } 1.217 + 1.218 + return *mParamsArray->begin(); 1.219 +} 1.220 + 1.221 +/** 1.222 + * If we are here then we know there are no pending async executions relying on 1.223 + * us (StatementData holds a reference to us; this also goes for our own 1.224 + * AsyncStatementFinalizer which proxies its release to the calling thread) and 1.225 + * so it is always safe to destroy our sqlite3_stmt if one exists. We can be 1.226 + * destroyed on the caller thread by garbage-collection/reference counting or on 1.227 + * the async thread by the last execution of a statement that already lost its 1.228 + * main-thread refs. 1.229 + */ 1.230 +AsyncStatement::~AsyncStatement() 1.231 +{ 1.232 + destructorAsyncFinalize(); 1.233 + cleanupJSHelpers(); 1.234 + 1.235 + // If we are getting destroyed on the wrong thread, proxy the connection 1.236 + // release to the right thread. I'm not sure why we do this. 1.237 + bool onCallingThread = false; 1.238 + (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onCallingThread); 1.239 + if (!onCallingThread) { 1.240 + // NS_ProxyRelase only magic forgets for us if mDBConnection is an 1.241 + // nsCOMPtr. Which it is not; it's an nsRefPtr. 1.242 + Connection *forgottenConn = nullptr; 1.243 + mDBConnection.swap(forgottenConn); 1.244 + (void)::NS_ProxyRelease(forgottenConn->threadOpenedOn, 1.245 + static_cast<mozIStorageConnection *>(forgottenConn)); 1.246 + } 1.247 +} 1.248 + 1.249 +void 1.250 +AsyncStatement::cleanupJSHelpers() 1.251 +{ 1.252 + // We are considered dead at this point, so any wrappers for row or params 1.253 + // need to lose their reference to us. 1.254 + if (mStatementParamsHolder) { 1.255 + nsCOMPtr<nsIXPConnectWrappedNative> wrapper = 1.256 + do_QueryInterface(mStatementParamsHolder); 1.257 + nsCOMPtr<mozIStorageStatementParams> iParams = 1.258 + do_QueryWrappedNative(wrapper); 1.259 + AsyncStatementParams *params = 1.260 + static_cast<AsyncStatementParams *>(iParams.get()); 1.261 + params->mStatement = nullptr; 1.262 + mStatementParamsHolder = nullptr; 1.263 + } 1.264 +} 1.265 + 1.266 +//////////////////////////////////////////////////////////////////////////////// 1.267 +//// nsISupports 1.268 + 1.269 +NS_IMPL_ADDREF(AsyncStatement) 1.270 +NS_IMPL_RELEASE(AsyncStatement) 1.271 + 1.272 +NS_INTERFACE_MAP_BEGIN(AsyncStatement) 1.273 + NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement) 1.274 + NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement) 1.275 + NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams) 1.276 + NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal) 1.277 + if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { 1.278 + foundInterface = static_cast<nsIClassInfo *>(&sAsyncStatementClassInfo); 1.279 + } 1.280 + else 1.281 + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageAsyncStatement) 1.282 +NS_INTERFACE_MAP_END 1.283 + 1.284 + 1.285 +//////////////////////////////////////////////////////////////////////////////// 1.286 +//// StorageBaseStatementInternal 1.287 + 1.288 +Connection * 1.289 +AsyncStatement::getOwner() 1.290 +{ 1.291 + return mDBConnection; 1.292 +} 1.293 + 1.294 +int 1.295 +AsyncStatement::getAsyncStatement(sqlite3_stmt **_stmt) 1.296 +{ 1.297 +#ifdef DEBUG 1.298 + // Make sure we are never called on the connection's owning thread. 1.299 + bool onOpenedThread = false; 1.300 + (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onOpenedThread); 1.301 + NS_ASSERTION(!onOpenedThread, 1.302 + "We should only be called on the async thread!"); 1.303 +#endif 1.304 + 1.305 + if (!mAsyncStatement) { 1.306 + int rc = mDBConnection->prepareStatement(mNativeConnection, mSQLString, 1.307 + &mAsyncStatement); 1.308 + if (rc != SQLITE_OK) { 1.309 + PR_LOG(gStorageLog, PR_LOG_ERROR, 1.310 + ("Sqlite statement prepare error: %d '%s'", rc, 1.311 + ::sqlite3_errmsg(mNativeConnection))); 1.312 + PR_LOG(gStorageLog, PR_LOG_ERROR, 1.313 + ("Statement was: '%s'", mSQLString.get())); 1.314 + *_stmt = nullptr; 1.315 + return rc; 1.316 + } 1.317 + PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Initialized statement '%s' (0x%p)", 1.318 + mSQLString.get(), 1.319 + mAsyncStatement)); 1.320 + } 1.321 + 1.322 + *_stmt = mAsyncStatement; 1.323 + return SQLITE_OK; 1.324 +} 1.325 + 1.326 +nsresult 1.327 +AsyncStatement::getAsynchronousStatementData(StatementData &_data) 1.328 +{ 1.329 + if (mFinalized) 1.330 + return NS_ERROR_UNEXPECTED; 1.331 + 1.332 + // Pass null for the sqlite3_stmt; it will be requested on demand from the 1.333 + // async thread. 1.334 + _data = StatementData(nullptr, bindingParamsArray(), this); 1.335 + 1.336 + return NS_OK; 1.337 +} 1.338 + 1.339 +already_AddRefed<mozIStorageBindingParams> 1.340 +AsyncStatement::newBindingParams(mozIStorageBindingParamsArray *aOwner) 1.341 +{ 1.342 + if (mFinalized) 1.343 + return nullptr; 1.344 + 1.345 + nsCOMPtr<mozIStorageBindingParams> params(new AsyncBindingParams(aOwner)); 1.346 + return params.forget(); 1.347 +} 1.348 + 1.349 + 1.350 +//////////////////////////////////////////////////////////////////////////////// 1.351 +//// mozIStorageAsyncStatement 1.352 + 1.353 +// (nothing is specific to mozIStorageAsyncStatement) 1.354 + 1.355 +//////////////////////////////////////////////////////////////////////////////// 1.356 +//// StorageBaseStatementInternal 1.357 + 1.358 +// proxy to StorageBaseStatementInternal using its define helper. 1.359 +MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL( 1.360 + AsyncStatement, 1.361 + if (mFinalized) return NS_ERROR_UNEXPECTED;) 1.362 + 1.363 +NS_IMETHODIMP 1.364 +AsyncStatement::Finalize() 1.365 +{ 1.366 + if (mFinalized) 1.367 + return NS_OK; 1.368 + 1.369 + mFinalized = true; 1.370 + 1.371 + PR_LOG(gStorageLog, PR_LOG_NOTICE, ("Finalizing statement '%s'", 1.372 + mSQLString.get())); 1.373 + 1.374 + asyncFinalize(); 1.375 + cleanupJSHelpers(); 1.376 + 1.377 + return NS_OK; 1.378 +} 1.379 + 1.380 +NS_IMETHODIMP 1.381 +AsyncStatement::BindParameters(mozIStorageBindingParamsArray *aParameters) 1.382 +{ 1.383 + if (mFinalized) 1.384 + return NS_ERROR_UNEXPECTED; 1.385 + 1.386 + BindingParamsArray *array = static_cast<BindingParamsArray *>(aParameters); 1.387 + if (array->getOwner() != this) 1.388 + return NS_ERROR_UNEXPECTED; 1.389 + 1.390 + if (array->length() == 0) 1.391 + return NS_ERROR_UNEXPECTED; 1.392 + 1.393 + mParamsArray = array; 1.394 + mParamsArray->lock(); 1.395 + 1.396 + return NS_OK; 1.397 +} 1.398 + 1.399 +NS_IMETHODIMP 1.400 +AsyncStatement::GetState(int32_t *_state) 1.401 +{ 1.402 + if (mFinalized) 1.403 + *_state = MOZ_STORAGE_STATEMENT_INVALID; 1.404 + else 1.405 + *_state = MOZ_STORAGE_STATEMENT_READY; 1.406 + 1.407 + return NS_OK; 1.408 +} 1.409 + 1.410 +//////////////////////////////////////////////////////////////////////////////// 1.411 +//// mozIStorageBindingParams 1.412 + 1.413 +BOILERPLATE_BIND_PROXIES( 1.414 + AsyncStatement, 1.415 + if (mFinalized) return NS_ERROR_UNEXPECTED; 1.416 +) 1.417 + 1.418 +} // namespace storage 1.419 +} // namespace mozilla