mozStorageAsyncStatement.cpp (10990B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include <limits.h> 8 9 #include "nsError.h" 10 #include "nsProxyRelease.h" 11 #include "nsThreadUtils.h" 12 #include "nsIClassInfoImpl.h" 13 #include "Variant.h" 14 15 #include "mozStorageBindingParams.h" 16 #include "mozStorageConnection.h" 17 #include "mozStorageAsyncStatementJSHelper.h" 18 #include "mozStorageAsyncStatementParams.h" 19 #include "mozStoragePrivateHelpers.h" 20 #include "mozStorageStatementRow.h" 21 #include "mozStorageStatement.h" 22 23 #include "mozilla/Logging.h" 24 25 extern mozilla::LazyLogModule gStorageLog; 26 27 namespace mozilla { 28 namespace storage { 29 30 //////////////////////////////////////////////////////////////////////////////// 31 //// nsIClassInfo 32 33 NS_IMPL_CI_INTERFACE_GETTER(AsyncStatement, mozIStorageAsyncStatement, 34 mozIStorageBaseStatement, mozIStorageBindingParams, 35 mozilla::storage::StorageBaseStatementInternal) 36 37 class AsyncStatementClassInfo : public nsIClassInfo { 38 public: 39 constexpr AsyncStatementClassInfo() = default; 40 41 NS_DECL_ISUPPORTS_INHERITED 42 43 NS_IMETHOD 44 GetInterfaces(nsTArray<nsIID>& _array) override { 45 return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement)(_array); 46 } 47 48 NS_IMETHOD 49 GetScriptableHelper(nsIXPCScriptable** _helper) override { 50 static AsyncStatementJSHelper sJSHelper; 51 *_helper = &sJSHelper; 52 return NS_OK; 53 } 54 55 NS_IMETHOD 56 GetContractID(nsACString& aContractID) override { 57 aContractID.SetIsVoid(true); 58 return NS_OK; 59 } 60 61 NS_IMETHOD 62 GetClassDescription(nsACString& aDesc) override { 63 aDesc.SetIsVoid(true); 64 return NS_OK; 65 } 66 67 NS_IMETHOD 68 GetClassID(nsCID** _id) override { 69 *_id = nullptr; 70 return NS_OK; 71 } 72 73 NS_IMETHOD 74 GetFlags(uint32_t* _flags) override { 75 *_flags = 0; 76 return NS_OK; 77 } 78 79 NS_IMETHOD 80 GetClassIDNoAlloc(nsCID* _cid) override { return NS_ERROR_NOT_AVAILABLE; } 81 }; 82 83 NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::AddRef() { 84 return 2; 85 } 86 NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::Release() { 87 return 1; 88 } 89 NS_IMPL_QUERY_INTERFACE(AsyncStatementClassInfo, nsIClassInfo) 90 91 static AsyncStatementClassInfo sAsyncStatementClassInfo; 92 93 //////////////////////////////////////////////////////////////////////////////// 94 //// AsyncStatement 95 96 AsyncStatement::AsyncStatement() : mFinalized(false) {} 97 98 nsresult AsyncStatement::initialize(Connection* aDBConnection, 99 sqlite3* aNativeConnection, 100 const nsACString& aSQLStatement) { 101 MOZ_ASSERT(aDBConnection, "No database connection given!"); 102 MOZ_ASSERT(aDBConnection->isConnectionReadyOnThisThread(), 103 "Database connection should be valid"); 104 MOZ_ASSERT(aNativeConnection, "No native connection given!"); 105 106 mDBConnection = aDBConnection; 107 mNativeConnection = aNativeConnection; 108 mSQLString = aSQLStatement; 109 110 MOZ_LOG(gStorageLog, LogLevel::Debug, 111 ("Inited async statement '%s' (0x%p)", mSQLString.get(), this)); 112 113 #ifdef DEBUG 114 // We want to try and test for LIKE and that consumers are using 115 // escapeStringForLIKE instead of just trusting user input. The idea to 116 // check to see if they are binding a parameter after like instead of just 117 // using a string. We only do this in debug builds because it's expensive! 118 auto c = nsCaseInsensitiveCStringComparator; 119 nsACString::const_iterator start, end, e; 120 aSQLStatement.BeginReading(start); 121 aSQLStatement.EndReading(end); 122 e = end; 123 while (::FindInReadable(" LIKE"_ns, start, e, c)) { 124 // We have a LIKE in here, so we perform our tests 125 // FindInReadable moves the iterator, so we have to get a new one for 126 // each test we perform. 127 nsACString::const_iterator s1, s2, s3; 128 s1 = s2 = s3 = start; 129 130 if (!(::FindInReadable(" LIKE ?"_ns, s1, end, c) || 131 ::FindInReadable(" LIKE :"_ns, s2, end, c) || 132 ::FindInReadable(" LIKE @"_ns, s3, end, c))) { 133 // At this point, we didn't find a LIKE statement followed by ?, :, 134 // or @, all of which are valid characters for binding a parameter. 135 // We will warn the consumer that they may not be safely using LIKE. 136 NS_WARNING( 137 "Unsafe use of LIKE detected! Please ensure that you " 138 "are using mozIStorageAsyncStatement::escapeStringForLIKE " 139 "and that you are binding that result to the statement " 140 "to prevent SQL injection attacks."); 141 } 142 143 // resetting start and e 144 start = e; 145 e = end; 146 } 147 #endif 148 149 return NS_OK; 150 } 151 152 mozIStorageBindingParams* AsyncStatement::getParams() { 153 nsresult rv; 154 155 // If we do not have an array object yet, make it. 156 if (!mParamsArray) { 157 nsCOMPtr<mozIStorageBindingParamsArray> array; 158 rv = NewBindingParamsArray(getter_AddRefs(array)); 159 NS_ENSURE_SUCCESS(rv, nullptr); 160 161 mParamsArray = static_cast<BindingParamsArray*>(array.get()); 162 } 163 164 // If there isn't already any rows added, we'll have to add one to use. 165 if (mParamsArray->length() == 0) { 166 RefPtr<AsyncBindingParams> params(new AsyncBindingParams(mParamsArray)); 167 NS_ENSURE_TRUE(params, nullptr); 168 169 rv = mParamsArray->AddParams(params); 170 NS_ENSURE_SUCCESS(rv, nullptr); 171 172 // We have to unlock our params because AddParams locks them. This is safe 173 // because no reference to the params object was, or ever will be given out. 174 params->unlock(nullptr); 175 176 // We also want to lock our array at this point - we don't want anything to 177 // be added to it. 178 mParamsArray->lock(); 179 } 180 181 return *mParamsArray->begin(); 182 } 183 184 /** 185 * If we are here then we know there are no pending async executions relying on 186 * us (StatementData holds a reference to us; this also goes for our own 187 * AsyncStatementFinalizer which proxies its release to the calling event 188 * target) and so it is always safe to destroy our sqlite3_stmt if one exists. 189 * We can be destroyed on the caller event target by 190 * garbage-collection/reference counting or on the async event target by the 191 * last execution of a statement that already lost its main-thread refs. 192 */ 193 AsyncStatement::~AsyncStatement() { 194 destructorAsyncFinalize(); 195 196 // If we are getting destroyed on the wrong event target, proxy the connection 197 // release to the right one. 198 if (!IsOnCurrentSerialEventTarget(mDBConnection->eventTargetOpenedOn)) { 199 // NS_ProxyRelase only magic forgets for us if mDBConnection is an 200 // nsCOMPtr. Which it is not; it's a RefPtr. 201 nsCOMPtr<nsIEventTarget> target(mDBConnection->eventTargetOpenedOn); 202 NS_ProxyRelease("AsyncStatement::mDBConnection", target, 203 mDBConnection.forget()); 204 } 205 } 206 207 //////////////////////////////////////////////////////////////////////////////// 208 //// nsISupports 209 210 NS_IMPL_ADDREF(AsyncStatement) 211 NS_IMPL_RELEASE(AsyncStatement) 212 213 NS_INTERFACE_MAP_BEGIN(AsyncStatement) 214 NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement) 215 NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement) 216 NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams) 217 NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal) 218 if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { 219 foundInterface = static_cast<nsIClassInfo*>(&sAsyncStatementClassInfo); 220 } else 221 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageAsyncStatement) 222 NS_INTERFACE_MAP_END 223 224 //////////////////////////////////////////////////////////////////////////////// 225 //// StorageBaseStatementInternal 226 227 Connection* AsyncStatement::getOwner() { return mDBConnection; } 228 229 int AsyncStatement::getAsyncStatement(sqlite3_stmt** _stmt) { 230 #ifdef DEBUG 231 // Make sure we are never called on the connection's owning event target. 232 NS_ASSERTION( 233 !IsOnCurrentSerialEventTarget(mDBConnection->eventTargetOpenedOn), 234 "We should only be called on the async event target!"); 235 #endif 236 237 if (!mAsyncStatement) { 238 int rc = mDBConnection->prepareStatement(mNativeConnection, mSQLString, 239 &mAsyncStatement); 240 if (rc != SQLITE_OK) { 241 MOZ_LOG(gStorageLog, LogLevel::Error, 242 ("Sqlite statement prepare error: %d '%s'", rc, 243 ::sqlite3_errmsg(mNativeConnection))); 244 MOZ_LOG(gStorageLog, LogLevel::Error, 245 ("Statement was: '%s'", mSQLString.get())); 246 *_stmt = nullptr; 247 return rc; 248 } 249 MOZ_LOG(gStorageLog, LogLevel::Debug, 250 ("Initialized statement '%s' (0x%p)", mSQLString.get(), 251 mAsyncStatement)); 252 } 253 254 *_stmt = mAsyncStatement; 255 return SQLITE_OK; 256 } 257 258 nsresult AsyncStatement::getAsynchronousStatementData(StatementData& _data) { 259 if (mFinalized) return NS_ERROR_UNEXPECTED; 260 261 // Pass null for the sqlite3_stmt; it will be requested on demand from the 262 // async event target. 263 _data = StatementData(nullptr, bindingParamsArray(), this); 264 265 return NS_OK; 266 } 267 268 already_AddRefed<mozIStorageBindingParams> AsyncStatement::newBindingParams( 269 mozIStorageBindingParamsArray* aOwner) { 270 if (mFinalized) return nullptr; 271 272 nsCOMPtr<mozIStorageBindingParams> params(new AsyncBindingParams(aOwner)); 273 return params.forget(); 274 } 275 276 //////////////////////////////////////////////////////////////////////////////// 277 //// mozIStorageAsyncStatement 278 279 // (nothing is specific to mozIStorageAsyncStatement) 280 281 //////////////////////////////////////////////////////////////////////////////// 282 //// StorageBaseStatementInternal 283 284 // proxy to StorageBaseStatementInternal using its define helper. 285 MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL( 286 AsyncStatement, if (mFinalized) return NS_ERROR_UNEXPECTED;) 287 288 NS_IMETHODIMP 289 AsyncStatement::Finalize() { 290 if (mFinalized) return NS_OK; 291 292 mFinalized = true; 293 294 MOZ_LOG(gStorageLog, LogLevel::Debug, 295 ("Finalizing statement '%s'", mSQLString.get())); 296 297 asyncFinalize(); 298 299 // Release the params holder, so it can release the reference to us. 300 mStatementParamsHolder = nullptr; 301 302 return NS_OK; 303 } 304 305 NS_IMETHODIMP 306 AsyncStatement::BindParameters(mozIStorageBindingParamsArray* aParameters) { 307 if (mFinalized) return NS_ERROR_UNEXPECTED; 308 309 BindingParamsArray* array = static_cast<BindingParamsArray*>(aParameters); 310 if (array->getOwner() != this) return NS_ERROR_UNEXPECTED; 311 312 if (array->length() == 0) return NS_ERROR_UNEXPECTED; 313 314 mParamsArray = array; 315 mParamsArray->lock(); 316 317 return NS_OK; 318 } 319 320 NS_IMETHODIMP 321 AsyncStatement::GetState(int32_t* _state) { 322 if (mFinalized) 323 *_state = MOZ_STORAGE_STATEMENT_INVALID; 324 else 325 *_state = MOZ_STORAGE_STATEMENT_READY; 326 327 return NS_OK; 328 } 329 330 //////////////////////////////////////////////////////////////////////////////// 331 //// mozIStorageBindingParams 332 333 BOILERPLATE_BIND_PROXIES(AsyncStatement, 334 if (mFinalized) return NS_ERROR_UNEXPECTED;) 335 336 } // namespace storage 337 } // namespace mozilla