Bug 1696969 [wpt PR 27896] - Allow fuzzy matching for replaced-element-003, a=testonly
[gecko.git] / storage / mozStorageAsyncStatement.cpp
blobba050e9d333fdec10f4f86130804e86cdff276aa
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/. */
7 #include <limits.h>
8 #include <stdio.h>
10 #include "nsError.h"
11 #include "nsMemory.h"
12 #include "nsProxyRelease.h"
13 #include "nsThreadUtils.h"
14 #include "nsIClassInfoImpl.h"
15 #include "Variant.h"
17 #include "mozStorageBindingParams.h"
18 #include "mozStorageConnection.h"
19 #include "mozStorageAsyncStatementJSHelper.h"
20 #include "mozStorageAsyncStatementParams.h"
21 #include "mozStoragePrivateHelpers.h"
22 #include "mozStorageStatementRow.h"
23 #include "mozStorageStatement.h"
25 #include "mozilla/Logging.h"
27 extern mozilla::LazyLogModule gStorageLog;
29 namespace mozilla {
30 namespace storage {
32 ////////////////////////////////////////////////////////////////////////////////
33 //// nsIClassInfo
35 NS_IMPL_CI_INTERFACE_GETTER(AsyncStatement, mozIStorageAsyncStatement,
36 mozIStorageBaseStatement, mozIStorageBindingParams,
37 mozilla::storage::StorageBaseStatementInternal)
39 class AsyncStatementClassInfo : public nsIClassInfo {
40 public:
41 constexpr AsyncStatementClassInfo() {}
43 NS_DECL_ISUPPORTS_INHERITED
45 NS_IMETHOD
46 GetInterfaces(nsTArray<nsIID>& _array) override {
47 return NS_CI_INTERFACE_GETTER_NAME(AsyncStatement)(_array);
50 NS_IMETHOD
51 GetScriptableHelper(nsIXPCScriptable** _helper) override {
52 static AsyncStatementJSHelper sJSHelper;
53 *_helper = &sJSHelper;
54 return NS_OK;
57 NS_IMETHOD
58 GetContractID(nsACString& aContractID) override {
59 aContractID.SetIsVoid(true);
60 return NS_OK;
63 NS_IMETHOD
64 GetClassDescription(nsACString& aDesc) override {
65 aDesc.SetIsVoid(true);
66 return NS_OK;
69 NS_IMETHOD
70 GetClassID(nsCID** _id) override {
71 *_id = nullptr;
72 return NS_OK;
75 NS_IMETHOD
76 GetFlags(uint32_t* _flags) override {
77 *_flags = 0;
78 return NS_OK;
81 NS_IMETHOD
82 GetClassIDNoAlloc(nsCID* _cid) override { return NS_ERROR_NOT_AVAILABLE; }
85 NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::AddRef() {
86 return 2;
88 NS_IMETHODIMP_(MozExternalRefCountType) AsyncStatementClassInfo::Release() {
89 return 1;
91 NS_IMPL_QUERY_INTERFACE(AsyncStatementClassInfo, nsIClassInfo)
93 static AsyncStatementClassInfo sAsyncStatementClassInfo;
95 ////////////////////////////////////////////////////////////////////////////////
96 //// AsyncStatement
98 AsyncStatement::AsyncStatement()
99 : StorageBaseStatementInternal(), mFinalized(false) {}
101 nsresult AsyncStatement::initialize(Connection* aDBConnection,
102 sqlite3* aNativeConnection,
103 const nsACString& aSQLStatement) {
104 MOZ_ASSERT(aDBConnection, "No database connection given!");
105 MOZ_ASSERT(aDBConnection->isConnectionReadyOnThisThread(),
106 "Database connection should be valid");
107 MOZ_ASSERT(aNativeConnection, "No native connection given!");
109 mDBConnection = aDBConnection;
110 mNativeConnection = aNativeConnection;
111 mSQLString = aSQLStatement;
113 MOZ_LOG(gStorageLog, LogLevel::Debug,
114 ("Inited async statement '%s' (0x%p)", mSQLString.get(), this));
116 #ifdef DEBUG
117 // We want to try and test for LIKE and that consumers are using
118 // escapeStringForLIKE instead of just trusting user input. The idea to
119 // check to see if they are binding a parameter after like instead of just
120 // using a string. We only do this in debug builds because it's expensive!
121 auto c = nsCaseInsensitiveCStringComparator;
122 nsACString::const_iterator start, end, e;
123 aSQLStatement.BeginReading(start);
124 aSQLStatement.EndReading(end);
125 e = end;
126 while (::FindInReadable(" LIKE"_ns, start, e, c)) {
127 // We have a LIKE in here, so we perform our tests
128 // FindInReadable moves the iterator, so we have to get a new one for
129 // each test we perform.
130 nsACString::const_iterator s1, s2, s3;
131 s1 = s2 = s3 = start;
133 if (!(::FindInReadable(" LIKE ?"_ns, s1, end, c) ||
134 ::FindInReadable(" LIKE :"_ns, s2, end, c) ||
135 ::FindInReadable(" LIKE @"_ns, s3, end, c))) {
136 // At this point, we didn't find a LIKE statement followed by ?, :,
137 // or @, all of which are valid characters for binding a parameter.
138 // We will warn the consumer that they may not be safely using LIKE.
139 NS_WARNING(
140 "Unsafe use of LIKE detected! Please ensure that you "
141 "are using mozIStorageAsyncStatement::escapeStringForLIKE "
142 "and that you are binding that result to the statement "
143 "to prevent SQL injection attacks.");
146 // resetting start and e
147 start = e;
148 e = end;
150 #endif
152 return NS_OK;
155 mozIStorageBindingParams* AsyncStatement::getParams() {
156 nsresult rv;
158 // If we do not have an array object yet, make it.
159 if (!mParamsArray) {
160 nsCOMPtr<mozIStorageBindingParamsArray> array;
161 rv = NewBindingParamsArray(getter_AddRefs(array));
162 NS_ENSURE_SUCCESS(rv, nullptr);
164 mParamsArray = static_cast<BindingParamsArray*>(array.get());
167 // If there isn't already any rows added, we'll have to add one to use.
168 if (mParamsArray->length() == 0) {
169 RefPtr<AsyncBindingParams> params(new AsyncBindingParams(mParamsArray));
170 NS_ENSURE_TRUE(params, nullptr);
172 rv = mParamsArray->AddParams(params);
173 NS_ENSURE_SUCCESS(rv, nullptr);
175 // We have to unlock our params because AddParams locks them. This is safe
176 // because no reference to the params object was, or ever will be given out.
177 params->unlock(nullptr);
179 // We also want to lock our array at this point - we don't want anything to
180 // be added to it.
181 mParamsArray->lock();
184 return *mParamsArray->begin();
188 * If we are here then we know there are no pending async executions relying on
189 * us (StatementData holds a reference to us; this also goes for our own
190 * AsyncStatementFinalizer which proxies its release to the calling thread) and
191 * so it is always safe to destroy our sqlite3_stmt if one exists. We can be
192 * destroyed on the caller thread by garbage-collection/reference counting or on
193 * the async thread by the last execution of a statement that already lost its
194 * main-thread refs.
196 AsyncStatement::~AsyncStatement() {
197 destructorAsyncFinalize();
199 // If we are getting destroyed on the wrong thread, proxy the connection
200 // release to the right thread. I'm not sure why we do this.
201 bool onCallingThread = false;
202 (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onCallingThread);
203 if (!onCallingThread) {
204 // NS_ProxyRelase only magic forgets for us if mDBConnection is an
205 // nsCOMPtr. Which it is not; it's an nsRefPtr.
206 nsCOMPtr<nsIThread> targetThread(mDBConnection->threadOpenedOn);
207 NS_ProxyRelease("AsyncStatement::mDBConnection", targetThread,
208 mDBConnection.forget());
212 ////////////////////////////////////////////////////////////////////////////////
213 //// nsISupports
215 NS_IMPL_ADDREF(AsyncStatement)
216 NS_IMPL_RELEASE(AsyncStatement)
218 NS_INTERFACE_MAP_BEGIN(AsyncStatement)
219 NS_INTERFACE_MAP_ENTRY(mozIStorageAsyncStatement)
220 NS_INTERFACE_MAP_ENTRY(mozIStorageBaseStatement)
221 NS_INTERFACE_MAP_ENTRY(mozIStorageBindingParams)
222 NS_INTERFACE_MAP_ENTRY(mozilla::storage::StorageBaseStatementInternal)
223 if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
224 foundInterface = static_cast<nsIClassInfo*>(&sAsyncStatementClassInfo);
225 } else
226 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, mozIStorageAsyncStatement)
227 NS_INTERFACE_MAP_END
229 ////////////////////////////////////////////////////////////////////////////////
230 //// StorageBaseStatementInternal
232 Connection* AsyncStatement::getOwner() { return mDBConnection; }
234 int AsyncStatement::getAsyncStatement(sqlite3_stmt** _stmt) {
235 #ifdef DEBUG
236 // Make sure we are never called on the connection's owning thread.
237 bool onOpenedThread = false;
238 (void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&onOpenedThread);
239 NS_ASSERTION(!onOpenedThread,
240 "We should only be called on the async thread!");
241 #endif
243 if (!mAsyncStatement) {
244 int rc = mDBConnection->prepareStatement(mNativeConnection, mSQLString,
245 &mAsyncStatement);
246 if (rc != SQLITE_OK) {
247 MOZ_LOG(gStorageLog, LogLevel::Error,
248 ("Sqlite statement prepare error: %d '%s'", rc,
249 ::sqlite3_errmsg(mNativeConnection)));
250 MOZ_LOG(gStorageLog, LogLevel::Error,
251 ("Statement was: '%s'", mSQLString.get()));
252 *_stmt = nullptr;
253 return rc;
255 MOZ_LOG(gStorageLog, LogLevel::Debug,
256 ("Initialized statement '%s' (0x%p)", mSQLString.get(),
257 mAsyncStatement));
260 *_stmt = mAsyncStatement;
261 return SQLITE_OK;
264 nsresult AsyncStatement::getAsynchronousStatementData(StatementData& _data) {
265 if (mFinalized) return NS_ERROR_UNEXPECTED;
267 // Pass null for the sqlite3_stmt; it will be requested on demand from the
268 // async thread.
269 _data = StatementData(nullptr, bindingParamsArray(), this);
271 return NS_OK;
274 already_AddRefed<mozIStorageBindingParams> AsyncStatement::newBindingParams(
275 mozIStorageBindingParamsArray* aOwner) {
276 if (mFinalized) return nullptr;
278 nsCOMPtr<mozIStorageBindingParams> params(new AsyncBindingParams(aOwner));
279 return params.forget();
282 ////////////////////////////////////////////////////////////////////////////////
283 //// mozIStorageAsyncStatement
285 // (nothing is specific to mozIStorageAsyncStatement)
287 ////////////////////////////////////////////////////////////////////////////////
288 //// StorageBaseStatementInternal
290 // proxy to StorageBaseStatementInternal using its define helper.
291 MIXIN_IMPL_STORAGEBASESTATEMENTINTERNAL(
292 AsyncStatement, if (mFinalized) return NS_ERROR_UNEXPECTED;)
294 NS_IMETHODIMP
295 AsyncStatement::Finalize() {
296 if (mFinalized) return NS_OK;
298 mFinalized = true;
300 MOZ_LOG(gStorageLog, LogLevel::Debug,
301 ("Finalizing statement '%s'", mSQLString.get()));
303 asyncFinalize();
305 // Release the params holder, so it can release the reference to us.
306 mStatementParamsHolder = nullptr;
308 return NS_OK;
311 NS_IMETHODIMP
312 AsyncStatement::BindParameters(mozIStorageBindingParamsArray* aParameters) {
313 if (mFinalized) return NS_ERROR_UNEXPECTED;
315 BindingParamsArray* array = static_cast<BindingParamsArray*>(aParameters);
316 if (array->getOwner() != this) return NS_ERROR_UNEXPECTED;
318 if (array->length() == 0) return NS_ERROR_UNEXPECTED;
320 mParamsArray = array;
321 mParamsArray->lock();
323 return NS_OK;
326 NS_IMETHODIMP
327 AsyncStatement::GetState(int32_t* _state) {
328 if (mFinalized)
329 *_state = MOZ_STORAGE_STATEMENT_INVALID;
330 else
331 *_state = MOZ_STORAGE_STATEMENT_READY;
333 return NS_OK;
336 ////////////////////////////////////////////////////////////////////////////////
337 //// mozIStorageBindingParams
339 BOILERPLATE_BIND_PROXIES(AsyncStatement,
340 if (mFinalized) return NS_ERROR_UNEXPECTED;)
342 } // namespace storage
343 } // namespace mozilla